一、线程同步安全锁(关键在于同步监听对象要一致,同步监听对象看作是锁,也就是说多个对象要使用的是同一把锁)
1、synchronized同步代码块锁(继承类线程和实现接口类线程都适用)
语法结构:synchronized(同步对象){
需要被锁住的代码(也就是容易发生线程安全的代码)
}
同步对象:指的是同步监听对象,看作是是一把锁
示例代码:
1 /** 2 * 解决线程同步安全问题最关键的是要使用同一把锁,也就是同一监听对象。 3 * 4 * 5 */ 6 public class Test1 { 7 public static void main(String[] args) { 8 TicketThread t1 = new TicketThread("梓沐"); 9 TicketThread t2 = new TicketThread("盂梦"); 10 TicketThread t3 = new TicketThread("哈哈"); 11 t1.start(); 12 t2.start(); 13 t3.start(); 14 } 15 16 } 17 /** 18 * 解决线程安全问题方法之一:synchronized同步代码块 19 * 语法结构: 20 * synchronized(同步对象){ 21 * 需要被锁住的代码 22 * } 23 * 同步对象:简单理解就是一把锁 24 * 注意:多个对象拿到的必须是同一把锁 25 * 我们在填充同步对象的时候,使用了new Object()也使用了this,发现都不可以,因为创建了3个对象 26 * 所以3个线程拿到的锁都不是同一把,而是3把不同的锁,所以是没有解决线程安全问题的 27 * 怎么解决呢? 28 * 答:使用字节码对象可以解决 29 * 字节码对象 简单理解 类型.class 30 * 以.java为后缀的文件 我们称为源文件 31 * 以.class为后缀的文件 我们称为字节码文件 32 * 把字节码文件放到jvm中 在jvm中它会存在一个字节码对象,就是你当前类型.class 33 * 字节码对象它是单例的,它在jvm中永远只会存在一份 34 * 35 */ 36 public class TicketThread extends Thread{ 37 static int num = 2000; 38 public TicketThread(String string) { 39 super(string); 40 } 41 @Override 42 public void run() { 43 //当synchronized锁住了整个while循环时,只有一个线程能进去,当切换到其它线程时,其他线程也不能进入,因此会只有一个窗口买票。 44 while(num>0){ 45 /* 46 * /当synchronized锁住了while循环中的执行语句时,此时会出现0票、-1票,原因是ABC三个线程都能进入while循环, 47 * 然后当A线程进入执行语句后,执行语句被锁上,此时A线程打印1,然后解锁执行语句,C线程进入,锁上执行语句,C线程打印0, 48 * 然后解锁执行语句,B线程进入,然后锁上执行语句,此时,B线程打印-1,租后解锁执行语句。因此,解决的办法是在同步代 49 * 码块里面加上if判断语句。 50 */ 51 synchronized (TicketThread.class) { 52 if(num>0){ 53 System.out.println(this.getName()+":"+num); 54 num--; 55 } 56 57 } 58 } 59 } 60 61 }
2、synchronized同步方法
/**
* 同步方法相关的同步监听对象问题:
* 如果你的方法是一个实例方法,那你同步监听对象就是当前类的对象this
* 如果你的方法是类的方法,那你同步监听对象就是当前类的字节码对象
* 字节码对象在jvm中永远只有1份
* 如果你实现的多线程,你是使用的继承方式,那就不能使用同步方法(因为在*类方法里面不能使用实例方法,也就是说在继承方式实现多线程时,我们不能 *知道线程的名称,因此不能满足我们的需求)
*必须使用同步代码块,因为同步方法不能解决我们的需求
*/
示例代码:
public class Test1 { public static void main(String[] args) { TicketThread t = new TicketThread(); Thread t1 = new Thread(t,"梓沐"); Thread t2 = new Thread(t,"盂梦"); Thread t3 = new Thread(t,"哈哈"); t1.start(); t2.start(); t3.start(); } } //解决线程安全问题之synchronized同步方法 public class TicketThread implements Runnable{ int num = 50; @Override public void run() { while(num>0){ saleTicket(); } } private synchronized void saleTicket() { if(num>0){ System.out.println(Thread.currentThread().getName()+num); num--; } /** * 同步方法相关的同步监听对象问题: * 如果你的方法是一个实例方法, 那你同步监听对象就是当前类的this * 如果你的方法是类的方法,那你同步监听对象就是当前类的字节码对象 * 字节码对象在jvm中永远只有1份 * 如果你实现的多线程,你是使用的继承方式,那就不能使用同步方法(因为在类方法里面不能使用实例方法,也就是说在继承方式实现多线程时, * 我们不能知道线程的名称,因此不能满足我们的需求) *必须使用同步代码块,因为同步方法不能解决我们的需求 */ } }
3、Lock加锁的方法(Lock是一个接口)
因为lock是一个接口,因此不能直接实例化,所以只能实例化它的子类ReentrantLock类
示例代码:
public class Test1 { public static void main(String[] args) { TicketThread t1 = new TicketThread("梓沐"); TicketThread t2 = new TicketThread("盂梦"); TicketThread t3 = new TicketThread("哈哈"); t1.start(); t2.start(); t3.start(); } } /**解决线程安全问题: * 解决线程同步方式三:使用Lock方式 * 语法格式: * Lock lock = new ReentrantLock() 生成一个lock对象 * lock.lock();//上锁 * try{ * 需要被锁住的代码 * }finally{ * lock.unlock();//释放锁资源 * } * * 线程同步前提: 多个线程要共享一把锁 */ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TicketThread extends Thread{ static Lock lock = new ReentrantLock(); static int num = 2000; public TicketThread(String string) { super(string); } @Override public void run() { while(num>0){ lock.lock(); //同步监听对象就是lock对象 try { if(num>0){ System.out.println(getName()+num); num--; } } finally { lock.unlock(); } } } }
二、join方法
1、void join() 当某个线程加入之后,其他线程就必须等着,一直要等着加入的线程执行完毕之后,其他线程才能继续执行。
三、守护线程
1、守护线程/精灵线程/后台线程:每个线程都可以或不可以标记为一个守护程序
2、后台线程仅仅就是对线程的一个分类或者标记。
3、后台线程作用:后台线程主要是给前台线程做服务的
示例:其实垃圾回收线程就是一个后台线程
4、后台线程特点: 当前台线程死亡之后,后台线程会自动死亡(注意:如果前台线程死了之后,后台线程不一定马上死亡 (因为很可能处理后事))
5、后台线程相关的方法:
boolean isDaemon() 测试该线程是否为守护线程。
void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6、主线程:
默认是前台线程
主线程只能是前台线程不能更改为后台线程
注意:当一个线程处于运行状态的时候,是不能更改它为后台线程的
7、自定义线程:
默认是后台线程还是前台线程是和它创建的环境线程完全一致
线程的优先级是和创建它的环境线程是一致的
四、线程通信和唤醒线程
1、线程的生命周期(创建线程对象、启动线程、运行线程、死亡(线程执行完毕或者异常没处理好))
2、线程通信指的是线程之间共享资源
3、synchronized同步方法下的线程通信和唤醒线程
代码实例:
public class Test1 { public static void main(String[] args) { Account account = new Account(0); SaveMoney saveMoney = new SaveMoney(account); GetMoney getMoney = new GetMoney(account); saveMoney.start(); getMoney.start(); } } public class SaveMoney extends Thread{ private Account account; public SaveMoney(Account account) { super(); this.account = account; } @Override public void run() { for (int i = 1; i <= 12; i++) { try { account.saveMoney(10000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public class GetMoney extends Thread{ private Account account; public GetMoney() { super(); } public GetMoney(Account account) { super(); this.account = account; } @Override public void run() { for (int i = 1; i <= 12; i++) { try { account.getMoney(10000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public class Account{ private int money; private boolean empty = true;//定义一个字段来标志是否有钱 public Account(int money) { super(); this.money = money; } public Account(){ } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public synchronized void saveMoney(int i) throws InterruptedException { if(!empty){ this.wait(); } System.out.println("存钱之前=========="+money); money += i; System.out.println("存钱之后=========="+money); empty = false; this.notify(); }//两个方法的同步监听对象(锁)是Account类的实例对象。 public synchronized void getMoney(int i) throws InterruptedException { if(empty){ this.wait(); } System.out.println("取钱之前=========="+money); money -= i; System.out.println("取钱之后=========="+money); empty = true; this.notify(); } }
4、lock方法下的线程通信和唤醒线程
示例代码:
public class Test1 { public static void main(String[] args) { Account account = new Account(0); SaveMoney saveMoney = new SaveMoney(account); GetMoney getMoney = new GetMoney(account); Thread t1= new Thread(saveMoney); Thread t2 = new Thread(getMoney); t1.start(); t2.start(); } } public class SaveMoney implements Runnable{ private Account account; public SaveMoney(Account account) { this.account = account; } @Override public void run() { for (int i = 1; i <= 12; i++) { account.saveMoney(1000); } } } public class GetMoney implements Runnable{ private Account account; public GetMoney(Account account) { this.account = account; } @Override public void run() { for (int i = 1; i <=12; i++) { account.getMoney(1000); } } } import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { private int money; private boolean empty = true; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); //返回一个与此lock实例一起使用的condition实例,Condition对象替换了Object类的wait和notify方法 /* * 使用lock时,同步监听对象就是lock。 * 线程通信与唤醒线程指的是synchronizesd同步方法中使用Object的wait方法(成为等待线程), * 或者notify方法(唤醒线程) * 或者lock方法中使用condition对象中的await方法和single方法。 * 注意:notify方法和single方法都只能一次唤醒一个线程 */ public Account() { } public Account(int money) { this.money = money; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public void saveMoney(int i) { lock.lock(); try { if(!empty){ try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("======存钱之前"+money); this.money+=i; System.out.println("======存钱之后"+money); empty = false; condition.signal(); } finally { lock.unlock(); } } public void getMoney(int i) { lock.lock(); try { if(empty){ try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("=====取钱之前"+money); this.money-=i; System.out.println("=====取钱之后"+money); empty = true; condition.signal(); } finally { condition.signal(); } } }
五、Timer定时器
代码实例:
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Timer; import java.util.TimerTask; public class Test1 { public static void main(String[] args) throws ParseException { //Timer() 创建一个新计时器。 Timer timer = new Timer(); //void schedule(TimerTask task, Date time) 安排在指定的时间执行指定的任务。 如果时间已过,启动之后马上执行 String str = "2019-04-09 11:36:00"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); timer.schedule(new TimerTask(){ //此处使用了匿名内部类,这样可以少些很多代码。匿名内部类只能new接口或者抽象类,由于抽象类和接口不能实例化,所以实际上new的是匿名内部类的对象 @Override public void run() { // TODO Auto-generated method stub System.out.println("定时炸弹"); } }, sdf.parse(str)); // void schedule(TimerTask task, Date firstTime, long period) 在指定的firstTime执行指定的task任务, //以后每个period时间重复执行,如果时间已过,启动之后,立刻执行,并且每隔period时间重复执行,不会把之前没有执行的代码执行。 timer.schedule(new TimerTask(){ @Override public void run() { System.out.println("我在指定时间执行"); } }, sdf.parse(str), 2000); //void schedule(TimerTask task, long delay, long period) 延迟delay时间执行task任务,每隔period时间又重复执行task任务 //如果时间已过,启动之后,立刻执行,并且每隔period时间重复执行,不会把之前没有执行的代码执行。 timer.schedule(new TimerTask(){ @Override public void run() { System.out.println("我在指定的延迟时间执行"); } }, sdf.parse(str), 2000); /* * void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 在firstTime的时间第一次执行task任务,以后每隔 * period时间重复执行task任务, 如果时间已过,它会把之前没有执行的代码,全部执行一次 */timer.scheduleAtFixedRate(new TimerTask(){ @Override public void run() { System.out.println("我会把之前没执行过的代码全部执行"); } }, sdf.parse(str), 2000); } }