java thread 线程
1、 创建线程
1.1、 Thread
继承 Thread,子类复写 run()
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i % 2 == 0 ) {
System.out.println(Thread.currentThread().getName() + "" + i);
}
}
}
}
Thread方法:
run(): 定义线程任务
start() : 启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务
currentThread(): 获取当前线程
getName() : 获取线程名
setName(String name) : 设置线程名
yield() : 当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态
join(): 让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行
sleep(long millis) : 线程休眠
// 优先级高的线程会更高的概率使用cpu
// public final static int MIN_PRIORITY = 1; 最小优先级
// public final static int NORM_PRIORITY = 5; 默认优先级
// public final static int MAX_PRIORITY = 10; 最大优先级
getPriority() : 获取线程优先级
setPriority(int priority) : 设置线程优先级
1.2 Runnable
实现 Runnable 接口, 复写 run() , 将该实现类的对象实例, 传给构造方法Thread(Runnable runnable)
class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
}
public class RunnableDemo01{
public static void main(String args[]){
MyThread mt1 = new MyThread() ; // 实例化对象
Thread t1 = new Thread(mt1) ; // 实例化Thread类对象
Thread t2 = new Thread(mt1) ; // 实例化Thread类对象
t1.setName("thread1")
t2.setName("thread2")
t1.start() ; // 启动多线程
t2.start() ; // 启动多线程
}
};
2 线程生命周期(状态)
1)新建:线程被创建出来,为调用 start()
2)就绪:调用start(),进入线程队列等待 cpu
3)运行:获取cpu使用权,进入运行状态
4)阻塞:在特殊情况下,被人为挂起或执行输入输出操作时,让出cpu 并暂时中断自己的执行,进入阻塞状态
5)死亡:线程完成了全部任务,或 被提前强制中止 或 出现异常 而结束线程。对于已经死亡的线程,无法再使用start方法令其进入就绪。
状态变化图:

3 线程安全
当操作共享数据时,有可能出现线程安全问题,解决方法: 使用同步,使得响应代码在同一时间只能一个线程执行。
三种方式:
1)同步代码块: synchronized(同步监视器) { /操作数据代码/ }
2)同步方法: 使用synchronized 修饰方法
3)使用Lock (ReentrantLock)
显而易见,使用同步后,总体执行效率会变低。
3.1 同步代码块
同步监视器,俗称,同步锁。同步锁可以是任何对象,但此对象必须相同唯一,即每个线程的锁是同一个。获取到锁的线程执行相应代码
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
synchronized(this){ // 要对当前对象进行同步, 同步对象可以是任何对象。可以使用 this , 或 类对象 如 MyThread.class
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
}
}
};
public class SyncDemo02{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
3.2 同步方法
当同步代码刚好是一个完整的部分时,可以使用 同步方法。同步方法的 同步对象不需要显示设置,非静态同步方法的同步对象是 this, 静态同步方法同步对象是 该类对象本身(MyThread.class)
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
this.sale() ; // 调用同步方法
}
}
public synchronized void sale(){ // 声明同步方法
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
};
public class SyncDemo03{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
同步的代码 不能多也不能少,要恰好。少了不能解决线程安全问题,多了降低效率
注意 通过 继承 Thread 创建的线程 和 实现 Runnable 的线程 在同步是略有不同的。要注意同步锁唯一。
通过 继承 Thread 创建的线程 的 同步方法需要是静态的static,这样才能保证 锁 唯一。
一个同步锁在同一个时刻只能供一个线程使用。
如果多个不同的线程有不同的同步代码但共用一个同步锁,那么谁先抢到锁谁运行,即这些不同的代码同一个时刻只能允许一个在执行。这线程不管是不是同一个类产生。
如下,Demo 中的 method1() method2() 在 t1 t2 线程启动后,只能有一个在运行
class Demo {
public synchronized void method1(){ // 声明同步方法
// 同步内容
}
public synchronized void method2(){ // 声明同步方法
// 同步内容
}
}
class Thread1 implements Runnable{
private Demo demo;
public Thread1 (Demo demo) {
this.demo= demo;
}
public void run(){
demo.method1()
}
}
class Thread2 implements Runnable{
private Demo demo;
public Thread2 (Demo demo) {
this.demo= demo;
}
public void run(){
demo.method2()
}
}
public class SyncDemo{
public static void main(String args[]){
Demo demo = new Demo()
Thread1 mt1 = new Thread1 (demo) ;
Thread2 mt2 = new Thread2 (demo) ;
Thread t1 = new Thread(mt1) ;
Thread t2 = new Thread(mt2) ;
t1.start() ;
t2.start() ;
}
};
3.3 ReentrantLock 锁
ReentrantLock 是 Lock 接口的实现类。 主要是 调用 lock.lock() 上锁, lock.unlock() 释放锁,处于二者之间的代码就是同步的
与 synchronized 修饰的 代码块 和 方法 区别:是需要手动释放锁
class MyThread extends Thread {
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();
if(i % 2 == 0 ) {
System.out.println(Thread.currentThread().getName() + "" + i);
}
lock.unlock();
}
}
}
3.4 死锁
就是两个线程都在等待对方先完成,造成程序的停滞,都处于阻塞状态
一个同步锁在同一个时刻只能供一个线程使用
一般出现死锁的情况: 线程中使用了有多个不同的锁,多个线程都在等在对方释放锁
尽量减少同步资源, 避免同步嵌套
4 线程通信
主要是相关方法的配套使用:wait() notify() notifyAll()
wait() : 执行此方法后,当前线程进入阻塞状态,并释放同步锁
notify(): 唤醒一个被 wait 的线程。 如果有多个被 wait 的线程, 唤醒优先级高的线程
notifyAll(): 唤醒所有被 wait 的线程
注意:wait() notify() notifyAll()
这三个方法是定义在 java.lang.Object 类中的,即所有对象都有此方法
这三个方法必须在 同步代码块 或 同步方法中使用
这三个方法的调用者是 同步监视器,即 同步锁
wait() sleep() 方法区别:都会使当前线程进入阻塞状态,但 sleep() 方法是 线程Thread的方法,可以在任何位置调用,并且如果在同步代码中调用,不会释放同步锁
class MyThread extends Thread {
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj) {
obj.notify();
if(ticket > 0 ) {
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
还有 一对方法: suspend() 和 resume()方法, 两个方法配套使用
suspend() : 使得线程进入阻塞状态,并且不会自动恢复
resume():使得被suspend()挂起的线程重新进入可执行状态
但这两个 API 是过期的,也就是不建议使用的。
不推荐使用 suspend() 去挂起线程的原因,是因为 suspend() 在导致线程暂停的同时,并不会去释放任何锁资源。其他线程都无法访问被它占用的锁。直到对应的线程执行 resume() 方法后,被挂起的线程才能继续,从而其它被阻塞在这个锁的线程才可以继续执行。
但是,如果 resume() 操作出现在 suspend() 之前执行,那么线程将一直处于挂起状态,同时一直占用锁,这就产生了死锁。而且,对于被挂起的线程,它的线程状态居然还是 Runnable。
5 使用Callable 接口创建线程
java5开始,提供了Callable接口,是Runable接口的增强版。同样用Call()方法作为线程的执行体,增强了之前的run()方法。因为call方法可以有返回值,也可以声明抛出异常。
Future接口: java5提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,所以这样可以作为Thread的target。
步骤
1.创建Callable的实现类,并冲写call()方法,该方法为线程执行体,并且该方法有返回值
2.创建Callable的实例,并用FutuerTask类来包装Callable对象,该FutuerTask封装了Callable对象call()方法的返回值
3.实例化FutuerTask类,参数为FutuerTask接口实现类的对象来启动线程
4.通过FutuerTask类的对象的get()方法来获取线程结束后的返回值
public class NewThread {
public static void main(String[] args) {
//先使用Lambda表达式创建Callable<Integer>对象,使用泛型
//并使用FutureTask来包装Callable对象
FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
//这里相当于call方法执行体。
int i=0;
for (i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"====="+i);
}
return i;
});
//创建一个线程,并start启动它。
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"======="+i);
if(i==0){
Thread t1=new Thread(task,"我是fu线程");
t1.start();
}
}
//得到返回值,注意这个必须要有显示抛出异常
try{
System.out.println("子线程的返回值"+task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
6 Executors 线程池
参考
https://www.cnblogs.com/java1024/archive/2019/11/28/11950129.html
https://www.cnblogs.com/jijijiefang/articles/7222955.html

浙公网安备 33010602011771号