线程的基本了解
1、线程的实现方式
线程有两种实现方式,分别为继承Thread、实现Runnable接口。差异性:实现方式避免了类的继承单一性,且对于多个线程同时访问同一个资源时更便捷。
(1)继承Thread
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class TestThread extends Thread { @Override public void run() { for (int i = 0; i <= 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } } public class TestThreadMethod { public static void main(String[] args) { TestThread th1 = new TestThread(); th1.setName("testThread"); th1.start(); } }
(2)实现Runnable接口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class TestRunnable implements Runnable{ @Override public void run() { for (int i=1; i <= 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } public class TestMain { public static void main(String[] args) { TestRunnable tr = new TestRunnable(); Thread th1 = new Thread(tr, "th1"); Thread th2 = new Thread(tr, "th2"); th1.start(); th2.start(); } }
(3)线程的生命周期
2、线程的常用方法
start():线程的开启。同一个线程在结束时只能开启一次,否则报错。
setName():设置当前线程的名称。
getName():获取当前线程的名称。
currentThread():获取当前线程的对象。
priority():线程的级别。静态值有,0/5(默认)/10。值越高,代表争夺CPU的使用权的几率越大,但不是绝对。
yield():线程让步。当前线程A调用yield()方法时,释放CPU的使用权,重新与其他线程B争夺CPU的使用权。
join():线程加入。在A线程中,调用B线程的join方法时,A线程的程序等待B线程的程序执行完才能继续执行A线程。
sleep():线程睡眠。当线程执行该方法时,进入设置的睡眠时间后继续执行程序。
isAlive():线程是否在活动,返回值为boolean。
wait():线程等待。必须在同步方法中使用。
notify()/notifyAll():线程唤醒,与wait()结合使用,用于线程通信。
注意:(1)线程在同步时,wait()会释放锁,而join()与sleep()方法不会释放锁。
(2)wait()、notify()、notifyAll()严格来说不算线程中的方法,这三个方法是定义在Object对象中。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class TestThread extends Thread { @Override public void run() { for (int i = 0; i <= 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } } public class TestThreadMethod { public static void main(String[] args) { TestThread th1 = new TestThread(); th1.setName("testThread"); th1.setPriority(Thread.MAX_PRIORITY); th1.start(); Thread.currentThread().setName("=======mainThread"); for (int i = 0; i <= 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); if (i % 10 == 0) { Thread.yield(); } if (i == 20) { try { th1.join(); System.out.println(th1.isAlive()); th1.start(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
3、线程的同步
线程的同步分为两种:同步代码块和同步方法块。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class TestThread extends Thread { static int ticket = 100; static Object obj = new Object(); @Override public void run() { /*1、同步代码块时,只能同步需要的代码块,其余无关代码块不行,会影响同步; * 2、同步监视器用this,代表当前对象,此时继承方式,对象是多个,所以无法同步; * 此两种情况出现重复输出情况,如下的this情况 */ /*synchronized (this) { while(true) { try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { // TODaO Auto-generated catch block e.printStackTrace(); } if (ticket <= 0) { break; } System.out.println(Thread.currentThread().getName() + "窗口:" +ticket--); } }*/ while(true) { synchronized (obj) {//obj充当锁时必须是静态的,否则多个对象代表的锁是各自的。 try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket <= 0) { break; } System.out.println(Thread.currentThread().getName() + "窗口:" +ticket--); } } } } public class TestMain { public static void main(String[] args) { TestThread th1 = new TestThread(); TestThread th2 = new TestThread(); th1.setName("th1"); th2.setName("th2"); th1.start(); th2.start(); } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class TestThread implements Runnable { int ticket = 100; @Override public void run() { while(true) { importNum(); } } /** * 同步方法块时,同步锁为当前对象.所以方法的类必须是同一个对象 */ private synchronized void importNum() { if (ticket <= 0) { return; } System.out.println(Thread.currentThread().getName() + "窗口:" + ticket--); } } public class TestMain { public static void main(String[] args) { TestThread th = new TestThread(); Thread th1 = new Thread(th, "th1"); Thread th2 = new Thread(th, "th2"); th1.start(); th2.start(); } }
注:同步在Thread与Runnable两种方式的实现中有很大的区别,详见上例。
拓展示例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class SingleTon { private SingleTon() { } public static SingleTon instance = null; public static SingleTon getSingleTon() { /** * 原程序直接判断,创建实例,返回。在多线程中,存在线程安全问题,会创建多个SingleTon; * 开发中为避免这情况发生,采用下面同步的实现方式。 */ /** if (instance == null) { instance = new SingleTon(); } return instance; */ if (instance == null) {//提高同步效率,后面访问方法的对象,无需再次等待 synchronized(SingleTon.class) {//采用类本身的对象来充当锁 if (instance == null) { instance = new SingleTon(); } } } return instance; } } public class TestMain { public static void main(String[] args) { SingleTon st1 = SingleTon.getSingleTon(); SingleTon st2 = SingleTon.getSingleTon(); } }
4、线程的死锁
死锁:当两个线程同时需要对方手中的锁时,各自都在等对方放弃手中的锁,而使程序无限期的等待下去,称为死锁。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class TestMain { public static void main(String[] args) { StringBuffer sb1 = new StringBuffer(); StringBuffer sb2 = new StringBuffer(); new Thread(){ public void run() { synchronized(sb1) { sb1.append("A"); try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(sb2) { sb2.append("B"); System.out.println("sb1:" + sb1.toString()); System.out.println("sb2:" + sb2.toString()); } } }; }.start(); new Thread(){ public void run() { synchronized(sb2) { sb2.append("C"); try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(sb1) { sb1.append("D"); System.out.println("sb1:" + sb1.toString()); System.out.println("sb2:" + sb2.toString()); } } }; }.start(); } }
上述例子中,第一个线程执行第一个同步块代码后,进行睡眠,握住sb1的锁;此时第二个线程可能已经开启运行,获取sb2的锁,进行同步代码块方法,进行睡眠。之后第一个线程睡眠时间过后要进行第二个同步代码块的执行,这时sb2的锁握在了第二个线程,而第二个线程在醒来后要获取第二块同步方法的锁时,这时的锁在第一个线程中。两个线程同时在等待对方释放手中的锁,导致了死锁的出现。
5、线程的通信
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 生产者 --> 店员(数量最多存放20) --> 消费者 * */ class Clerk { int num; public synchronized void addProduct() { if (num >= 20) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } notifyAll(); num++; System.out.println(Thread.currentThread().getName() + ":" + num); } public synchronized void consume() { if (num == 0) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } notifyAll(); System.out.println(Thread.currentThread().getName() + ":" + num); num--; } } class Customer implements Runnable { Clerk clerk; public Customer(Clerk clerk) { this.clerk = clerk; } public void run() { while(true) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } clerk.consume(); } } } class Producer implements Runnable { Clerk clerk; public Producer(Clerk clerk) { this.clerk = clerk; } public void run() { while(true) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } clerk.addProduct(); } } } public class TestMain { public static void main(String[] args) { Clerk clerk = new Clerk(); Customer c1 = new Customer(clerk); Producer p1 = new Producer(clerk); Thread th2 = new Thread(p1, "生产者"); Thread th1 = new Thread(c1, "消费者"); th2.start(); th1.start(); } }