一、Thread
1、常用方法
-
void start():启动线程,并执行对象的run()方法
-
run():线程在被调度时执行的操作
-
String getName():返回线程的名称
-
void setName(String name):设置该线程名称
-
static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
-
static void yield():线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有相同优先级的线程,忽略此方法 -
join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,知道join()方法加入的join线程执行完为止
低优先级的线程也可以获得执行 -
static void sleep(long millis):(指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重新排队
抛出InterruptedException异常 -
stop():强制线程生命期结束,已过时的方法
-
boolean isAlive():返回boolean,判断线程是否还活着
2、优先级
- 线程的优先等级
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 默认优先级 - 涉及的方法
getPriority():返回线程优先值
setPriority(int newPriority):改变线程的优先级 - 说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
二、线程的生命周期
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。java语言使用Thread类及其子类的对象来表示线程,在它的一个完整额生命周期中通常要经历如下五种状态:
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待的CPU时间片,此时它已具备了运行的条件只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
生命周期流程图
三、线程的安全问题(同步处理)
出现情况:当多个线程共用时,出现了线程进入判断语句阻塞的问题,导致共享数据没及时操作,而出现的问题
例:买票时,还剩下一张票,当某个线程未完成操作(已经进入关键代码的同时),另一线程完成了操作,而此时未完成的线程继续完成,就会导致多卖出了一张相同的票。
解决想法:当一个线程正在进行关键代码的操作时,其他线程不能进入,只有等上一线程完成后,才可进入关键代码的操作。
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码(关键代码)
}
说明:
- 操作共享数据的代码即为关键代码
- 共享数据:多个线程共同操作的数据
- 同步监视器,俗称:锁。任何一个类的对象都可以充当锁。要求:多个线程必须共用同一个锁
注意:操作关键代码时,处理的代码不要多余,更不要少于。
在实现Runnable接口创建多线程时,可以考虑使用this充当同步监视器。不过继承Thread类创建多线程时,慎用this。但是可以尝试使用当前类作为同步监视器。如:
synchronized(Thread.class){}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
说明:
- 同步方法任然涉及到同步监视器,只是不需要我们显式的声明
- 非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身
使用同步的方式的问题
- 好处:解决了线程的安全问题。
- 坏处:操作同步代码时只能有一个线程参与,其他线程等待,相当于是一个单线程的过程。
方式三:锁(lock)
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能一个线程对Lock对象加锁,线程开始访问共享资源之前应当先获得Lock对象。
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制送,比较常用的是ReentrantLock,可以显式加锁、释放锁。
ReentrantLock lock = new ReentrantLock();
//ReentrantLock lock = new ReentrantLock(true);让同步序列为队列方式
public void run(){
try{
//调用加锁
lock.lock();
//需要被同步的代码(关键代码)
}finally{
//调用解锁
lock.unlock();
}
}
synchronized与Lock的异同:
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码后,自动释放同步监视器,Lock需要手动开启同步(lock()),结束时也需要手动关闭同步(unlock())。
四、线程的创建
1、继承Thread类:同步方法实现线程安全
/**
* 创建多线程的第一种方式:继承Thread类
* 实现三个进程同时出票
*/
public class Thread01 {
public static void main(String[] args) {
MyThread01 myThread01 = new MyThread01();
MyThread01 myThread02 = new MyThread01();
MyThread01 myThread03 = new MyThread01();
myThread01.setName("线程一");
myThread02.setName("线程二");
myThread03.setName("线程三");
//启动当前线程,调用当前线程的run()
myThread01.start();
myThread02.start();
myThread03.start();
}
}
/**
* 继承Thread,重写run()
* run()中为当前线程的执行内容。
* 通过主方法main()实例化继承Thread的类,调用start()开启线程。
*/
class MyThread01 extends Thread{
private static int number = 100;
@Override
public void run() {
while (true){
show();
if (number == 0)
break;
}
}
private static synchronized void show(){
if (number > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + number);
number--;
}
}
}
2、实现Runnable接口:同步代码块实现线程安全
/**
* 创建多线程的方式二:实现Runnable接口
* 实现三个进程同时出票
*/
public class Thread02 {
public static void main(String[] args) {
MyThread02 m2 = new MyThread02();
//通过Thread类的构造器创建线程
Thread t1 = new Thread(m2);
Thread t2 = new Thread(m2);
Thread t3 = new Thread(m2);
t1.setName("线程一");
t2.setName("线程二");
t3.setName("线程三");
t1.start();
t2.start();
t3.start();
}
}
class MyThread02 implements Runnable{
private int number = 100;
@Override
public void run() {
while (true){
synchronized (this) {
if (number > 0) {
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + number);
number--;
} else {
break;
}
}
}
}
}
方式1、2之间的区别:
- 方式一为继承Thread类,每次开启线程都需要实例化一个对象,所以子类的属性都是每一个对象独有的。所以方式一最后出来的结果为三个100的数。若想实现共用一个100的数,则创建的属性number需设置为静态的。
- 方式二是实现Runnable接口,实现类只实例化一次,创建线程时,是通过Thread类的构造器实现的。所以例子中创建的三个线程共用一个100的数
说明:方式1、2为JDK5之前的创建方式;方式3、4为JDK5之后新增。
3、实现Callable接口:锁(lock)方式实现线程安全
与实现Runnable接口相比,实现Callable的优势在于:
- 相比run(),call()方法可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 借助FutureTask类,可以获取返回结果等。
/**
* 创建多线程的方式三:实现Callable接口
*/
public class Thread03 {
public static void main(String[] args) {
MyThread03 m3 = new MyThread03();
FutureTask<Integer> futureTask = new FutureTask<Integer>(m3);
FutureTask<Integer> f2 = new FutureTask<Integer>(m3);
//创建线程
Thread t1 = new Thread(futureTask);
Thread t2 = new Thread(f2);
t1.setName("线程一");
t2.setName("线程二");
//开启线程
t1.start();
t2.start();
try {
//获取返回值
Integer sum = futureTask.get();
System.out.println(t1.getName() + ":总和为" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyThread03 implements Callable{
private ReentrantLock lock = new ReentrantLock();
private int number = 100;
@Override
public Object call() throws Exception {
int sum = 0;
while (true) {
try {
lock.lock();
if (number > 0) {
System.out.println(Thread.currentThread().getName() + ":" + number);
sum += number;
if (Thread.currentThread().getName().equals("线程一"))
System.out.println("当前" + Thread.currentThread().getName() + "的总和为:" + sum);
number--;
}else{
break;
}
} finally {
lock.unlock();
}
}
return sum;
}
}
4、使用线程池
/**
* 创建多线程的方式四:线程池
*/
public class Thread04 {
public static void main(String[] args) {
//创建线程池,设置最大线程数
ExecutorService executorService = Executors.newFixedThreadPool(10);
//线程池的实现类,用于管理线程池的属性
//ThreadPoolExecutor executorService1 = (ThreadPoolExecutor) executorService;
//执行指定的线程操作,需要提供Runnable接口或Callable接口实现类的对象
executorService.execute(new MyThread04_1());//适用于Runnable接口
executorService.submit(new MyThread04_2());
//关闭线程池
executorService.shutdown();
}
}
class MyThread04_1 implements Runnable{
@Override
public void run() {
for (int i = 0;i < 100;i++){
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class MyThread04_2 implements Callable{
@Override
public Object call() throws Exception {
for (int i = 0;i < 100;i++){
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
return null;
}
}
五、线程的通信
涉及的方法(java.long.Objeck中):
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,就唤醒优先级高的线程。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
/**
* 使线程交互性的输出1~100的数
*/
public class Communication {
public static void main(String[] args) {
Number n = new Number();
Thread t1 = new Thread(n);
Thread t2 = new Thread(n);
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this) {
//解除被wait()阻塞的线程
notify();
if (number <= 100){
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使线程进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
说明:wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中;
三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常。
问题
-
sleep()和wait()的异同?
- 相同点:一旦执行方法,都可以使得线程进入阻塞状态
-
不同点:1)两个方法声明的位置不同:Thread类中声明的sleep(),Object类中声明的wait()
2)调用的要求不同:sleep()可以在任何场景中调用。wait()必须使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放,wait()会释放。