一、线程创建
方式一、继承Thread类
1.定义新类继承Thread类
2.重写run()方法
3.创建新类对象,调用 .start()方法
//方式1.1 继承Thread类
class thread1 extends Thread{
@Override
public void run() {
System.out.println("线程1的操作...");
}
}
@Test
public void test1(){
thread1 t1=new thread1();
t1.start();
}
//方式1.2 继承Thread类,但用匿名内部类实现
@Test
public void test2(){
//1.2 采用匿名子类的方式
new Thread(){
@Override
public void run() {
System.out.println("线程1的操作...");
}
}.start();
}
方式二、实现Runable接口
1.定义新类实现Runable接口
2.重写run()方法
3.创建Thread类的实例,构造器参数为新类
4.Thread类的实例调用 .start()方法
源码解读:Thread类的构造器有个Runnable类型参数target,调用Thread的run()时实际上执行的是target的run(),target就是我们自定义的类里面的run()
class thread2 implements Runnable{
@Override
public void run() {
System.out.println("线程2的操作...");
}
}
@Test
public void test3(){
Thread t2=new Thread(new thread2());
t2.start();
}
两种方式的比较:
-开发中一般用第二种方式
原因:1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况
联系:Thread 类也继承于 Runnable( )接口
相同点:两种方式都需要重写run(),将线程要执行的操作声明在run()中
方式三、实现 Callable 接口
与Runnable相比,Callable功能更强大:
-相比run()可以有返回值
-方法可以抛出异常
-支持泛型的返回值
-需要借助FutureTask类,比如获取返回结果
Future接口:
-可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
-FutureTask是Future接口的唯一实现类,同时实现了Runnable、Future接口
实现步骤:
1.创建 Callable的实现类
2.实现 call方法,将此线程需要执行的操作声明在call()中
3.创建Callable 接口实现类的对象
4.将Callable 接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread类构造器中,创建Thread对象,并调用start()
6.(可选)获取Callable中call方法的返回值 - .get
class Thread3 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("线程3的操作");
return 521;//可以有返回值
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread3 thread3=new Thread3();//新建线程的对象
FutureTask futureTask=new FutureTask(thread3);//FutureTask 对象
new Thread(futureTask).start();// Thread 对象.start()
futureTask.get();// 获取该线程的返回值
}
方式四、使用线程池
思路:提前创建好多个线程,放入线程池中,使用时直接获取,是用完放回池中
好处:提高响应速度、降低资源消耗、便于线程管理
相关API:
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
-void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable
-Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable
-void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池 Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
class Thread4 implements Runnable{
@Override
public void run() {
System.out.println("线程4");
}
}
class Thread5 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("线程5");
return 521;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService service= Executors.newFixedThreadPool(10);
service.execute(new Thread4());//执行实现Runnable接口的线程
service.submit(new Thread5());//执行实现Callable接口的线程
service.shutdown();//关闭所有线程
}
}
线程4
线程5
二、线程类的相关方法
1. start() :启动当前线程:调用当前线程的run()
2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3. currentThread():静态方法,返回执行当前代码的线程
4. getName():获取当前线程的名字
5. setName():设置当前线程的名字 可以在重写的Thread类的构造器中重命名
6. yield():释放当前cpu的执行权
7. join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完,a才结束阻塞状态
8. sleep():让当前线程睡眠指定毫秒的时间,使得当前线程进入阻塞状态,在Thread中声明
,不会释放锁(同步监视器)
三、线程的优先级
MAX_PRIORITY:10
NORM_PRIORITY:5 (默认值)
MIN_PRIORITY:1
高优先级的线程会抢占低优先级线程的cpu执行权,但这只是从概念上讲的,不是高优先级执行完才执行低优先级。
涉及的方法
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
四、线程的生命周期
-每个线程用有独立的运行栈和程序计数器pc
-一个进程中的多个线程共享相同的内存单元,他们从同一个堆中分配对象,可以访问相同的变量和对象
-java应用程序至少有三个线程:main()、 gc() 垃圾回收线程、异常处理线程
五、线程的同步
1.线程安全问题
下面是火车售票的例子,3个线程再判断是否有余票的时候都阻塞了,则恢复执行后数据就出错了:
解决方法:一般通过同步机制来解决线程的安全问题
-方式一、同步代码块
Synchronized(this/newthread.class){
//需要被同步的代码块
}
说明:1.操作共享数据(多个线程共同操作的变量)的代码即为需要被同步的代码
2.同步监视器:俗称锁,可以是任一个类的对象,
要求:多个线程必须共用一把锁
3.在实现Runnable接口创建多线程的方式中,可以使用 this 充当同步监视器,但继承Thread方式时不要用 this 作为锁,因为 this 会分别指代多个线程的对象,可以用 类.class 代替 this
-方式二、同步方法
-把需要同步的代码块拿出来单独作为一个方法,声明时加上synchronized.
private synchronized void test(){
// 需要被同步的代码块
}
总结:
1、同步方式的不足之处:操作同步代码时只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
2、同步方法仍然涉及到同步监视器,只是不需要显式的声明
3、非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前class本身
2.线程的死锁问题
死锁 :不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
解决方法 :
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
所以引入了方式三:
-方式三、Lock锁 (-jdk5.0+)
private final ReentrantLock lock = new ReenTrantLock();
try{
lock.lock();
//有隐患的代码块
}finally{
lock.unlock();
}
Lock与Synchronized 区别:
1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放
2. Lock只有代码块锁,synchronized有代码块锁和方法锁
3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
六、线程的通信
1.常用方法
wait()一旦执行,当前线程就进入阻塞状态,并释放同步监视器
notify()一旦执行,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的
notifyAll()一旦执行,就会唤醒被wait的所有线程
说明:
1.wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中(synchronized)
2.三者的调用者必须是同步代码块或同步方法中的同步监视器,否则出现异常
3.三者定义在object类中
wait()与 sleep()的对比:
1.sleep()再Thread类中声明,wait()在object类中声明。
2.sleep()可在任何场景下调用,wait()只能在同步代码块或同步方法中用
3.sleep()不释放同步锁,wait()会释放。
2.举例-生产者消费者模式
生产者负责生产产品,消费者负责消费产品,店员负责转交产品。店员取货和送货的速度都是随机的,全程不间断进行。
public class test0 {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Thread p1=new Thread(new Producer(clerk));
Thread c1=new Thread(new Customer(clerk));
p1.setName("1.生产者");
c1.setName("2.消费者");
p1.start();
c1.start();
}
}
class Clerk{
private int productCount=0;
Random random=new Random();
public synchronized void produceProduct() throws InterruptedException {
if(productCount<20){
productCount++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
Thread.sleep(random.nextInt(250));
notify();
}else {
wait();
}
}
public synchronized void consumeProduct() throws InterruptedException {
if(productCount>0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
productCount--;
Thread.sleep(random.nextInt(250));
notify();
}else {
wait();
}
}
}
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("开始生产产品...");
while (true){
try {
clerk.produceProduct();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Customer implements Runnable{
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("开始消费产品...");
while (true){
try {
clerk.consumeProduct();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}