1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 例1 :实现Runnable接口创建多线程;正确的程序。 class A implements Runnable { public int tickets = 10000 ; String str = new String("恒恒恒" ); public void run () { while (true ) { synchronized (str) { if (tickets > 0 ) { System.out.printf("%s线程正在卖出第%d张票\n" , Thread.currentThread().getName(), tickets); --tickets; } else { break ; } } } } } public class TestTickets_2 { public static void main (String[] args) { A aa = new A(); Thread t1 = new Thread(aa); t1.start(); Thread t2 = new Thread(aa); t2.start(); } } 理解:因为是实现了Runnable接口,所以不管是t1,t2都只是传递了类A造出来的一个对象aa,因此t1,t2所访问的tickets和str都是同一个类中的属性,并不用去设置为static 。所以Tickets相同,同步代码块锁定的是同一个对象,因此程序正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 例2 :直接继承Thread类的方法;正确的程序。 class A extends Thread { public static int tickets = 1000 ; public static String str = new String("哈哈" ); public void run () { while (true ) { synchronized (str) { if (tickets > 0 ) { System.out.printf("%s线程正在卖出第%d张票\n" , Thread.currentThread().getName(), tickets); --tickets; } else { break ; } } } } } public class TestTickets_3 { public static void main (String[] args) { A aa1 = new A(); aa1.start(); A aa2 = new A(); aa2.start(); } } 理解:继承Thread类的A类new 出来两个对象aa1和aa2,本该是每个对象都有一个类A的属性,但因为类A中的属性是static ,所以aa1和aa2共同拥有tickets和str,所以同步代码块的对象相同,出售的tickets相同,因此程序正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 例3 :直接继承Thread类的方法;正确的程序。 class A extends Thread { public static int tickets = 1000 ; public String str = new String("恒恒恒" ); public void run () { String str = "恒子" ; while (true ) { synchronized (str) { if (tickets > 0 ) { System.out.printf("%s线程正在卖出第%d张票\n" , Thread.currentThread().getName(), tickets); --tickets; } else { break ; } } } } } public class TestTickets_4 { public static void main (String[] args) { A aa1 = new A(); aa1.start(); A aa2 = new A(); aa2.start(); } } 理解:继承Thread类的A类new 出来两个对象aa1和aa2,本该是每个对象都有一个类A的属性,但因为类A中的tickets属性是static ,所以aa1和aa2共拥tickets属性,但str是非static 的,所以同步代码块中的对象并不是同一个对象,但是在执行run()方法时,又重新定义了在数据区的str,所以同步代码块绑定的还是同一个对象,因此该程序正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 例4 :调用Runnable接口的方法;正确的程序。 class A implements Runnable { public static int tickets = 1000 ; String str = new String("恒" ); public void run () { String str = "恒子" ; while (true ) { synchronized (str) { if (tickets > 0 ) { System.out.printf("%s线程正在卖出第%d张票\n" , Thread.currentThread().getName(), tickets); --tickets; } else { break ; } } } } } public class TestTickets_9 { public static void main (String[] args) { A aa = new A(); A bb = new A(); Thread t1 = new Thread(aa); t1.start(); Thread t2 = new Thread(bb); t2.start(); } } 理解:类A实现了Runnable接口,该程序与例1 相似,但该程序类A new 出了两个对象,因此开辟了两块空间,t1,t2的引用分别是aa和bb,并非同一个引用,但在类A中,Tickets是static 的,所以t1,t2还是共同拥有同一个Tickets,但str对于t1,t2来说就不是同一个属性,可是,在执行run()方法的时候,里面定义了在数据区的局部变量str,这又重新使得同步代码块中t1,t2所绑定的对象是同一个对象。因此该程序也是正确的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 例5 :实现Runnable接口的方法;错误的程序。 class A implements Runnable { public static int tickets = 1000 ; String str = new String("恒恒恒恒" ); public void run () { while (true ) { synchronized (str) { if (tickets > 0 ) { System.out.printf("%s线程正在卖出第%d张票\n" , Thread.currentThread().getName(), tickets); --tickets; } else { break ; } } } } } public class TestTickets_9 { public static void main (String[] args) { A aa = new A(); A bb = new A(); Thread t1 = new Thread(aa); t1.start(); Thread t2 = new Thread(bb); t2.start(); } } 理解:(对比例4 ),少了run()方法中的 String str = "恒子" ;所以同步代码块中的对象并未是统一对象,因此该程序错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 例6 :直接继承Thread类的方法;错误的程序。 class A extends Thread { public static int tickets = 1000 ; public String str = new String("恒恒恒" ); public void run () { while (true ) { synchronized (str) { if (tickets > 0 ) { System.out.printf("%s线程正在卖出第%d张票\n" , Thread.currentThread().getName(), tickets); --tickets; } else { break ; } } } } } public class TestTickets_4 { public static void main (String[] args) { A aa1 = new A(); aa1.start(); A aa2 = new A(); aa2.start(); } } 理解:(对比例3 )继承Thread类的A类new 出来两个对象aa1和aa2,本该是每个对象都有一个类A的属性,但因为类A中的tickets属性是static ,所以aa1和aa2共拥tickets属性,但str是非static 的,所以同步代码块中的对象并不是同一个对象,因此该程序错误。
Lock锁:
从JDK5.0开始,java提供了更强大的线程同步机制—通过显示定义同步锁对象来实现同步。同步锁使用lock对象充当。
java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对资源的独占访问,每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应先获得lock对象。
ReentrantLock锁实现了lock,它拥有与synchronized相同的并发性和内存定义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。
Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class Window implements Runnable { private int ticket = 100 ; private ReentrantLock lock = new ReentrantLock(); @Override public void run () { while (true ){ try { lock.lock(); if (ticket > 0 ){ try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; }else { break ; } }finally { lock.unlock(); } } } } public class LockTest { public static void main (String[] args) { Window w = new Window(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1" ); t2.setName("窗口2" ); t3.setName("窗口3" ); t1.start(); t2.start(); t3.start(); } }
小Tips: 解决线程安全问题,有几种方式?
3种,同步方法,同步代码块,lock锁。
优先使用顺序: Lock 锁—-> 同步代码块(已经进入了方法体,分配了相应资源)—>同步方法(在方法体之外)
synchronized 与 Lock的异同?
相同: 二者都可以解决线程安全的问题。
不同:
synchronized机制在执行完相应的同步代码以后,自动释放同步监视器。
Lock 需要手动的启动同步( lock() ),同时结束同步也需要手动的实现( unlock() )
synchronized与Lock的对比:
Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出现了作用域的自动释放。
Lock只有代码块锁,synchronized 有代码块锁和方法锁。
使用lock 锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
创建线程的方式三: 在上面我们提到了继承Thread类和实现Runnable接口两种方法来创建线程,但是创建线程的方法不只是这两种呦,而且在开发中我们也不能每次用到线程都去造把,不要重复造轮子我们也都或多或少听别人谈起过,但是偶尔造造轮子也会让你对代码有更深刻的理解呦。
下面我们看一下创建线程的第三种方式:
Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask class NumThread implements Callable { @Override public Object call () throws Exception { int sum = 0 ; for (int i = 1 ; i <= 100 ; i++) { if (i % 2 == 0 ){ System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main (String[] args) { NumThread numThread = new NumThread(); FutureTask futureTask = new FutureTask(numThread); new Thread(futureTask).start(); try { Object sum = futureTask.get(); System.out.println("总和为:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
创建线程的方式四: 这是我们开发中经常用的方式呦:
Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;class NumberThread implements Runnable { @Override public void run () { for (int i = 0 ;i <= 100 ;i++){ if (i % 2 == 0 ){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } class NumberThread1 implements Runnable { @Override public void run () { for (int i = 0 ;i <= 100 ;i++){ if (i % 2 != 0 ){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } public class ThreadPool { public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool(10 ); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; service.execute(new NumberThread()); service.execute(new NumberThread1()); service.shutdown(); } }