多线程
java线程的状态
创建线程的方式
1.继承Thread类
-
多线程常用方法
-
代码演示
public class MyThread extends Thread{ public MyThread(String name){ super(name); //1.执行父类Thread(String name)构造器,为当前线程设置名字了 } @Override public void run() { //2.currentThread() 哪个线程执行它,它就会得到哪个线程对象。 Thread t = Thread.currentThread(); for (int i = 1; i <= 3; i++) { //3.getName() 获取线程名称 System.out.println(t.getName() + "输出:" + i); } } }
public class ThreadTest1 { public static void main(String[] args) { Thread t1 = new MyThread(); t1.setName(String name) //设置线程名称; t1.start(); System.out.println(t1.getName()); //Thread-0 Thread t2 = new MyThread("2号线程"); // t2.setName("2号线程"); t2.start(); System.out.println(t2.getName()); // Thread-1 // 主线程对象的名字 // 哪个线程执行它,它就会得到哪个线程对象。 Thread m = Thread.currentThread(); m.setName("最牛的线程"); System.out.println(m.getName()); // main for (int i = 1; i <= 5; i++) { System.out.println(m.getName() + "线程输出:" + i); } } }
2.实现Runnable接口
-
使用步骤
1.先写一个Runnable接口的实现类,重写run方法(这里面就是线程要执行的代码)
2.再创建一个Runnable实现类的对象
3.创建一个Thread对象,把Runnable实现类的对象传递给Thread
4.调用Thread对象的start()方法启动线程(启动后会自动执行Runnable里面的run方法) -
代码演示
public class MyRunnable implements Runnable{ // 2、重写runnable的run方法 @Override public void run() { // 线程要执行的任务。 for (int i = 1; i <= 5; i++) { System.out.println("子线程输出 ===》" + i); } } }
public class ThreadTest2 { public static void main(String[] args) { // 3、创建任务对象。 Runnable target = new MyRunnable(); // 4、把任务对象交给一个线程对象处理。 // public Thread(Runnable target) new Thread(target).start(); for (int i = 1; i <= 5; i++) { System.out.println("主线程main输出 ===》" + i); } //匿名内部类简化 // 简化形式1: new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println("子线程2输出:" + i); } } }).start(); // 简化形式2: new Thread(() -> { for (int i = 1; i <= 5; i++) { System.out.println("子线程3输出:" + i); } }).start(); for (int i = 1; i <= 5; i++) { System.out.println("主线程main输出:" + i); } } }
3.实现Callable接口
-
使用步骤
1.先定义一个Callable接口的实现类,重写call方法(代码演示省略)
2.创建Callable实现类的对象
3.创建FutureTask类的对象,将Callable对象传递给FutureTask
4.创建Thread对象,将Future对象传递给Thread
5.调用Thread的start()方法启动线程(启动后会自动执行call方法)
等call()方法执行完之后,会自动将返回值结果封装到FutrueTask对象中6.调用FutrueTask对的get()方法获取返回结果
-
代码演示
public class ThreadTest3 { public static void main(String[] args) throws Exception { // 3、创建一个Callable的对象 Callable<String> call = new MyCallable(100); // 4、把Callable的对象封装成一个FutureTask对象(任务对象) // 未来任务对象的作用? // 1、是一个任务对象,实现了Runnable对象. // 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。 FutureTask<String> f1 = new FutureTask<>(call); // 5、把任务对象交给一个Thread对象 new Thread(f1).start(); Callable<String> call2 = new MyCallable(200); FutureTask<String> f2 = new FutureTask<>(call2); new Thread(f2).start(); // 6、获取线程执行完毕后返回的结果。 // 注意:如果执行到这儿,假如上面的线程还没有执行完毕 // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。 String rs = f1.get(); System.out.println(rs); String rs2 = f2.get(); System.out.println(rs2); } }
4创建线程池
-
ThreadPoolExecutor构造器及核心参数
ExecutorService pool = new ThreadPoolExecutor( int corePoolSize, //核心线程数 int maximumPoolSize, //最大线程数。 临时线程数=最大线程数-核心线程数=5-3=2 long keepAliveTime, //临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。 TimeUnit unit,//临时线程存活时间,时间单位(秒) BlockingQueue<Runnable> workQueue, //任务阻塞队列,没有来得及执行的任务在,任务队列中等待 ThreadFactory threadFactory, //用于创建线程的工厂对象 RejectExecutionHandler handler //拒绝策略 );
线程池执行Runnable任务
-
使用execute方法来执行Runnable任务
-
代码演示
ExecutorService pool = new ThreadPoolExecutor( 3, //核心线程数有3个 5, //最大线程数有5个。 临时线程数=最大线程数-核心线程数=5-3=2 8, //临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。 TimeUnit.SECONDS,//时间单位(秒) new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待 Executors.defaultThreadFactory(), //用于创建线程的工厂对象 new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略 ); Runnable target = new MyRunnable(); pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的! ...... //继续添加任务
线程池执行Callable任务
-
使用submit方法来执行Callable任务
-
代码演示
public class ThreadPoolTest2 { public static void main(String[] args) throws Exception { // 1、通过ThreadPoolExecutor创建一个线程池对象。 ExecutorService pool = new ThreadPoolExecutor( 3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); // 2、使用线程处理Callable任务。 Future<String> f1 = pool.submit(new MyCallable(100)); Future<String> f2 = pool.submit(new MyCallable(200)); // 3、执行完Callable任务后,需要获取返回结果。 System.out.println(f1.get()); System.out.println(f2.get()); } }
线程安全问题
1.同步代码块
//锁对象:必须是一个唯一的对象(同一个地址)
synchronized(锁对象){
//...访问共享数据的代码...
}
- 注意点:
1.建议把共享资源作为锁对象, 不要将随便无关的对象当做锁对象
2.对于实例方法,建议使用this作为锁对象
3.对于静态方法,建议把类的字节码(类名.class)当做锁对象
2.同步方法
概念:把整个方法给锁住,一个线程调用这个方法,另一个线程调用的时候就执行不了,只有等上一个线程调用结束,下一个线程调用才能继续执行。
public synchronized void drawMoney(double money) {
...... //方法体
}
- 同步方法也是有锁对象,只不过这个锁对象没有显示的写出来而已。
1.对于实例方法,锁对象其实是this(也就是方法的调用者)
2.对于静态方法,锁对象时类的字节码对象(类名.class)
3.Lock锁
1.首先在成员变量位子,需要创建一个Lock接口的实现类对象(这个对象就是锁对象)
private final Lock lk = new ReentrantLock();
2.在需要上锁的地方加入下面的代码
lk.lock(); // 加锁
//...中间是被锁住的代码...
lk.unlock(); // 解锁
没有显示的写出来而已。
1.对于实例方法,锁对象其实是this(也就是方法的调用者)
2.对于静态方法,锁对象时类的字节码对象(类名.class)
3.Lock锁
1.首先在成员变量位子,需要创建一个Lock接口的实现类对象(这个对象就是锁对象)
private final Lock lk = new ReentrantLock();
2.在需要上锁的地方加入下面的代码
lk.lock(); // 加锁
//...中间是被锁住的代码...
lk.unlock(); // 解锁