多线程学习:
一、线程和进程
线程是程序的一次执行过程,或是正在运行的一个程序。
进程可进一步细化为线程,是一个程序内部的一条执行路径。
二、并行和并发
并行:多个CPU同时执行多个任务。比如多个人做不同的事情
并发:一个CPU(采用时间片)同时执行多个任务。比如秒杀、多个人做同一件事
三、何时需要多线程
程序需要同时执行两个或多个任务;
程序需要实现一些需要等待的任务,比如用户输入、文件读取操作、网络操作、搜索等
需要一些后台运行的程序时
四、线程的创建和使用
①继承Thread重写run()方法
将需要执行的语句写在run中,使用start()方法进行启动线程
1、创建Thread的子类
2、重写run()方法
3、创建子类对象
4、调用start()方法启动线程
// 1.创建一个Thread的子类
class MyThread extends Thread {
// 2.重写Thread中run()
@Override
public void run(){
for (int i = 1; i < 100; i++) {
if(i % 2 == 0){
System.out.println("偶数:"+i+"--->"+Thread.currentThread().getName());
}
}
}
}
public class ThreadTest {
public static void main(String[] args){
// 3.创建Thread类的子类的对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 4、通过此对象调用start() :①启动当前线程,②调用当前线程的run()
t1.start();
// 问题一:
// 不能使用直接调用run()的方式启动线程,
// t1.run();
// 问题二:
// 不能再重新启动一个线程,
// 再重新启动一个线程
t2.start();
// 以下属于在main线程中执行的
for (int i = 1; i < 100; i++) {
if(i % 2 != 0){
System.out.println("不是偶数:"+i+"--->"+Thread.currentThread().getName());
}
}
}
}
②实现Runnable接口
1、创建实现类
2、实现接口抽象方法run()
3、创建实现类的对象
4、将实现类的对象作为参数传递到Thread的构造器中
5、调用Thread中的start()方法启动线程
// 1.创建一个实现Runnable接口的实现类
class MyRunnable implements Runnable{
// 2.实现run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
public class RunnableTest1 {
public static void main(String[] args) {
// 3.创建实现类的对象
MyRunnable myRunnable = new MyRunnable();
// 4.Runnable实现类对象作为参数传递到Thread的构造器中
Thread t1 = new Thread(myRunnable);
// 5.通过start()方法进行启动线程
t1.start();
}
}
③实现callable接口
1、创建一个实现callable接口的实现类
2、实现call()方法,将此线程需要执行的操作放到call()中
3、创建callable实现类的对象
4、将callable实现类对象作为参数传递到FutureTask构造器中,创建FutureTask对象
5、将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,
6、调用start()启动线程
7、获取callable的返回值(看需求,可选)
// 1.创建一个实现Callable接口的实现类
class NumThread implements Callable{
// 2.实现call()方法
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+"-->偶数:"+i);
sum += i;
}
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
// 3.创建Callable实现类的对象
NumThread numThread = new NumThread();
// 4.将Callable实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
// 5.将FutureTask对象作为参数传递到Thread构造器中,创建Thread对象,
// 6.调用start()方法启动线程
new Thread(futureTask).start();
try {
// 7. 获取callable的返回值(看需求,可选)
Object sum = futureTask.get();
System.out.println("偶数和为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
④使用线程池
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
步骤:
1、提供指定线程数量的线程池
2、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口的实现类对象。
3、关闭线程池
public class ThreadPollTest {
public static void main(String[] args) {
// 1.提供指定线程池数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 2.执行指定的线程操作。需要提供实现Runnable接口或者Callable接口的实现类对象
service.execute(new NumberTest()); // 适用于Runnable
service.execute(new NumberTest()); // 适用于Runnable
// service.submit(); // 适用于Callable
// 3.关闭连接池
service.shutdown();
}
}
class NumberTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
4.1 继承Thread类重写run()
常用方法:
start() ---> 启动当前线程,调用当前线程的run方法
run() ---> 通常需要重写Thread中的run 方法,将创建的线程需要执行的语句写在此方法中
currentThread() ---> 静态方法,返回执行当前代码的线程
getName() ---> 获取当前线程的名称
setName() ---> 设置当前线程的名称
yield() ---> 释放当前CPU的执行权,线程让步
join() ---> 在线程a中调用线程b的join()方法,此时线程a就会进入阻塞状态,直到线程
b执行完成后在进行执行线程a。
stop() ---> 当执行此方法时强制结束当前线程(已经过时)
sleep() ---> 让当前线程睡眠指定的时间,
isAlive() ---> 判断当前线程是否存活
4.2 继承Thread和实现Runnable
在开发中优先选择实现Runnable接口的方式
原因:
①实现的方式没有类的单继承的局限性
②实现的方式更适合处理多个线程有共享数据的情况
两种方法都需要重写run()方法。
五、线程的生命周期
①新建
②就绪
③运行
④阻塞
⑤死亡
六、线程的同步
在Java中线程的同步主要是解决线程的安全问题。
实现同步的方式有:
方式一:同步代码块
synchronized(同步监视器){
// 需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码
共享数据:多个线程操作的变量
2.同步监视器:俗称,锁。任何一个类的对象都可以作为锁。
要求:多个线程必须使用同一把锁。
3.在实现Runnable接口创建的多线程的方式中可以使用this充当同步监视器
4.在继承Thread类创建的多线程的方式中可以使用当前类本身(类名.class)充当同步监视器
class Window extends Thread {
private static int ticket = 100;
@Override
public void run(){
while (true){
// 在继承Thread方式中使用同步代码块解决线程安全问题
synchronized(Window.class){
if (ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"卖票,票号为:"+ ticket);
ticket--;
}else{
break;
}
}
}
}
}
class Window1 implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while (true){
// 在实现Runnable接口方式中使用同步代码块解决线程安全问题
synchronized(this){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
1.同步方法仍然涉及到同步监视器,不需要我们显示声明
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器:当前类本身
继承Thread接口方式创建多线程中使用同步方法
private static synchronized void show(){
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ ticket);
ticket--;
}
}
实现Runnable接口方式创建多线程中使用同步方法
private synchronized void show(){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号:"+ticket);
ticket--;
}
}
方式三:lock锁
1、实例化一个ReentrantLock
2、调用锁定方法lock()
3、调用解锁方法unlock()
class Window5 implements Runnable{
private static int ticket = 100;
// 1.实例化一个ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){
try {
// 2.调用锁定方法lock()
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 {
// 3.调用解锁方法unlock()
lock.unlock();
}
}
}
}
缺点:
效率低
6.21 线程的死锁问题
1、什么是死锁
不同的线程分别占用对方需要的同步资源不放弃,
都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2、说明:
1. 出现死锁后,不会出现异常,不会出现提示,只是所的线程都处于阻塞状态,无法继续
2. 我们使用同步时,要避免出现死锁。
3、避免方法
①专门的算法、原则
②尽量减少同步资源的定义
③尽量避免嵌套同步
七、线程的通信
1.wait() --> 执行此方法当前线程进入阻塞状态,并且释放同步监视器
2.notify() --> 执行该方法会唤醒一个使用wait()阻塞的线程
3.notifyAll() --> 执行该方法唤醒所有使用wait()阻塞的线程。