Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例
Thread类常用方法
构造方法
-
public Thread():分配一个新的线程对象。
-
public Thread(String name):分配一个指定名字的新的线程对象。
-
public Thread(Runnable target):分配一个带有指定目标新的线程对象。
-
public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
常用方法
-
public string getName():获取当前线程名称。
-
public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法。
-
public void run():此线程要执行的任务在此处定义代码。
-
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
-
public static Thread currentThread():返回对当前正在执行的线程对象的引用。
创建线程方式一
Java中通过继承Thread类来创建并启动多线程的步骤如下:
-
定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
-
创建Thread子类的实例,即创建了线程对象
-
调用线程对象的start()方法来启动该线程
测试类:
public class Demo{ public static void main(String[] args) { // 创建自定义线程对象 MyThread mt = new MyThread("新线程"); // 开启新线程 mt.start(); // 在主方法中执行for循环 for (int i = 0; i < 10; i++) { System.out.println("main线程正在执行" + i); } } }
自定义线程类:
public class MyThread extends Thread { // 定义指定线程名称的构造方法 public MyThread(String name) { // 调用父类的String参数的构造方法,指定线程的名称 super(name); } /** * 重写run方法,完成该线程执行的逻辑 */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + "正在执行" + i); } } }
流程图:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。
随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。
创建线程方式二
Java中通过实现Runnable接口来创建并启动多线程的步骤如下:
-
定义Runnable接口的实现类,并重写该接口的run () 方法,该run () 方法的方法体同样是该线程的线程执行体。
-
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
-
调用线程对象的start () 方法来启动线程。
测试类:
public class Demo { public static void main(String[] args) { // 创建自定义类对象线程任务对象 MyRunnable mr = new MyRunnable(); // 创建线程对象 Thread t = new Thread(mr, "新线程"); t.start(); for (int i = 0; i < 10; i++) { System.out.println("main线程正在执行" + i); } } }
自定义线程类:
public class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "正在执行" + i); } } }
通过实现Runnable接口,使得该类有了多线程类的特征。run () 方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
-
适合多个相同的程序代码的线程去共享同一个资源。
-
可以避免java中的单继承的局限性。
-
增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
-
线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类。
匿名内部类方式实现线程的创建
public class Demo { public static void main(String[] args) { //使用匿名内部类方法;直接创建Thread类的子类对象 /* * new Thread() { public void run() { for (int i = 0; i < 10; i++) { * System.out.println("新线程正在执行" + i); } } }.start(); */ //使用匿名内部类方式;直接创建Runnable接口实现类对象 Runnable r = new Runnable() { public void run() { for (int i = 0; i < 10; i++) { System.out.println("新线程正在执行" + i); } } }; new Thread(r).start(); for (int i = 0; i < 10; i++) { System.out.println("main线程正在执行" + i); } } }
线程安全
两个或两个以上的线程在访问共享资源时,仍然能得到正确的结果则称之为线程安全
模拟卖50张电影票
public class Ticket implements Runnable { private int ticket = 50; // 买票操作 @Override public void run() { // 每个窗口买票操作,窗口永远开启 while (true) { if (ticket > 0) { // 使用sleep方法模拟买票 try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 获取当前对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖票:" + ticket--); } } } }
测试类:
public class Demo { public static void main(String[] args) { // 创建线程任务对象 Ticket ticket = new Ticket(); // 创建三个窗口卖票 Thread t1 = new Thread(ticket, "窗口一"); Thread t2 = new Thread(ticket, "窗口二"); Thread t3 = new Thread(ticket, "窗口三"); // 同时开始卖票 t1.start(); t2.start(); t3.start(); } }
结果出现了这种现象:
这种问题,几个窗口(线程)票数不同步了,称为线程不安全
线程同步
当我们使用多个线程访问统一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题.
要解决上述多线程并发访问多一个资源的安全性问题,java中提供了同步机制(synchronized)来解决,有三种方式完成同步操作:
-
同步代码块
-
同步方法
-
锁机制
同步代码块
同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问
synchronized(同步锁){ 需要同步操作的代码 }
同步锁注意事项
1.锁对象可以是任意类型。
2.多个线程对象要使用同一把锁。
同步代码块实现线程安全
public class Ticket implements Runnable { private int ticket = 50; Object lock = new Object(); // 买票操作 @Override public void run() { // 每个窗口买票操作,窗口永远开启 while (true) { synchronized (lock) { if (ticket > 0) { // 使用sleep方法模拟买票 try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 获取当前对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖票:" + ticket--); } } } } }
同步方法
同步方法:使用synchronized修饰的方法,就叫做同步方法保证A线程执行该方法的时候,其他线程只能在方法外等着
public synchronized void method(){ 可能会产生线程安全问题的代码 }
同步方法实现线程安全
public class Ticket implements Runnable { private int ticket = 50; // 买票操作 @Override public void run() { // 每个窗口买票操作,窗口永远开启 while (true) { sellTicket(); } } // 锁对象是谁调用这个方法就是谁,this public synchronized void sellTicket() { if (ticket > 0) { // 使用sleep方法模拟买票 try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 获取当前对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖票:" + ticket--); } } }
Lock锁
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。 Lock常用方法
-
public void lock():加同步锁。
-
public void unlock():释放同步锁。
Lock锁实现线程安全
public class Ticket implements Runnable { private int ticket = 50; Lock lock = new ReentrantLock(); // 买票操作 @Override public void run() { // 每个窗口买票操作,窗口永远开启 while (true) { lock.lock(); if (ticket > 0) { // 使用sleep方法模拟买票 try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 获取当前对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖票:" + ticket--); } lock.unlock(); } } }
线程池
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
-
降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
-
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-
提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存
线程池的使用
Java里面线程池的顶级接口是java.util.concurrent.Executors
public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
public Future> submit(Runnable task) :获取线程池中的某一个线程对象,并执行
Runnable实现类代码:
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("我要一个教练"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("教练来了: " + Thread.currentThread().getName()); System.out.println("教我游泳,交完后,教练回到了游泳池"); } }
线程池测试类:
public class Demo{ public static void main(String[] args) { // 创建线程池对象 ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象 // 创建Runnable实例对象 MyRunnable r = new MyRunnable(); //自己创建线程对象的方式 // Thread t = new Thread(r); // t.start(); ---> 调用MyRunnable中的run() // 从线程池中获取线程对象,然后调用MyRunnable中的run() service.submit(r); // 再获取个线程对象,调用MyRunnable中的run() service.submit(r); service.submit(r); // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。 // 将使用完的线程又归还到了线程池中 // 关闭线程池 //service.shutdown(); } }