目录
十三、wait()、notify()、notifyAll()
一、程序、进程、线程
- 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,有它自身的生命周期。
- 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。一个进程可以包含多个线程。
二、并行和并发的概念
- 并行:是指同一时刻多个任务同时在运行,是真意义上的同时运行。
- 并发:是指多个任务交替使用CPU,从这个时间段上看似乎这些任务在同时运行,但实际上在某一时刻只有一个任务在运行。
三、用继承Thread类的方式创建多线程
步骤:
- 定义一个子类继承Thread类。
- 子类中重写run()方法。
- 在主线程中new一个子类对象。
- 调用该子类对象的start()方法。
下面在main中创建了一个新的线程用于遍历0——50的偶数
-
package com.hedong;
-
-
//自定义一个子类继承Thread类
-
class FirstThread extends Thread{
-
//重写run方法
-
@Override
-
public void run() {
-
//遍历输出0——50的偶数
-
for (
int i =
0; i <
50; i++) {
-
if(i %
2 ==
0){
-
System.out.println(i);
-
}
-
}
-
}
-
}
-
-
/**
-
* @author hedong
-
* @version 1.0
-
* @date 2020/4/8 8:21
-
*/
-
public
class MyThread {
-
-
public static void main(String[] args) {
-
//创建子类对象,即一个线程对象
-
FirstThread firstThread=
new FirstThread();
-
//调用线程对相爱那个的start方法
-
firstThread.start();
-
}
-
}
注意:
- 想要启动多线程,必须调用start()方法。如果手动将调用start()方法的地方改为调用run()方法,那么程序将不会启动多线程模式。
- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出异常“IllegalThreadStateException”。
四、用实现Runnable接口的方式创建多线程
步骤:
- 定义一个子类实现Runnable接口。
- 子类中重写Runnable的run()方法。
- 在主线程中new一个子类对象。
- 将子类对象作为实际参数传递给Thread类的含参构造器中创建线程对象。
- 调用该线程对象的start()方法。
-
package com.hedong;
-
-
//自定义一个子类实现Runnable接口
-
class FirstThread implements Runnable{
-
//重写run方法
-
@Override
-
public void run() {
-
//遍历输出0——50的偶数
-
for (
int i =
0; i <
50; i++) {
-
if(i %
2 ==
0){
-
System.out.println(i);
-
}
-
}
-
}
-
}
-
-
/**
-
* @author hedong
-
* @version 1.0
-
* @date 2020/4/8 8:21
-
*/
-
public
class MyThread {
-
-
public static void main(String[] args) {
-
//创建子类对象
-
FirstThread firstThread=
new FirstThread();
-
//将子类对象作为一个参数放入Thread的含参构造器中,生成一个线程对象
-
Thread myThread=
new Thread(firstThread);
-
//调用线程对象的start方法
-
myThread.start();
-
}
-
}
五、 继承方式和实现方式的联系与区别
联系:通过源码可以发现Thread类实际上也实现了Runnable接口。
区别:继承Thread类的方式是将线程代码放在Thread子类的run()方法中。而实现Runnable接口的方式是将线程代码放在Runnable接口子类的run()方法中。
实现Runnable接口的方式的优点:用此种方式实现的多个线程可以共享实现Runnable接口子类中定义的对象。简单来说就是多个线程共用同一份数据资源。
六、线程的生命周期
七、线程安全问题
问题举例:假如卡里原先有3000元,A、B两人同时取这张卡里的钱,两人的操作视作两个不同的线程。A打算取2000元,线程A进入if判断3000>2000后发生阻塞,此时B打算也取2000元,并且线程B在线程A发生阻塞的时候顺利取走了2000元,此时卡里只剩1000元,等线程A阻塞结束后继续扣去卡里2000元,这时悲剧发生:卡里最终金额为-1000。
八、线程安全问题的解决——Synchronized同步机制
Java对于多线程的安全问题提供了专业的解决方式:同步机制。同步机制可以分为同步代码块和同步方法。
1、同步代码块:代码如下,同步监视器相当于一把锁,这个锁可以为任意的类对象,若多线程是用实现Runnable接口的方式实现,则考虑使用this充当对象,若多线程是用继承Thread类的方式实现,则考虑使用当前类充当对象。其中需要被同步的代码即为run()方法中的代码。注意:多个线程必须使用同一把锁,即同一个对象。
-
synchronized (同步监视器){
-
// 需要被同步的代码;
-
}
- 同步代码块处理实现Runnable接口方式的线程安全的问题:
-
//自定义一个子类实现Runnable接口
-
class FirstThread implements Runnable{
-
//重写run方法
-
@Override
-
public void run() {
-
//遍历输出0——50的偶数
-
synchronized(
this) {
-
for (
int i =
0; i <
50; i++) {
-
if(i %
2 ==
0){
-
System.out.println(i);
-
}
-
}
-
}
-
}
-
}
- 同步代码块处理继承Thread类方式的线程安全的问题:
-
//自定义一个子类继承Thread类
-
class FirstThread extends Thread{
-
//重写run方法
-
@Override
-
public void run() {
-
synchronized (FirstThread.class){
//
-
for (
int i =
0; i <
50; i++) {
-
if(i %
2 ==
0){
-
System.out.println(i);
-
}
-
}
-
}
-
}
-
}
2、同步方法:synchronized还可以放在方法声明中,表示整个方法为同步方法。同步方法仍然涉及到同步监视器,只是不用我们自己去声明,当为非静态的同步方法时,同步监视器是this;当为静态的同步方法时,同步监视器是当前类。
- 同步方法处理实现Runnable接口方式的线程安全的问题:
-
//自定义一个子类实现Runnable接口
-
class FirstThread implements Runnable{
-
//重写run方法
-
@Override
-
public void run() {
-
show();
-
}
-
-
private synchronized void show(){
//此时同步监视器为:this
-
for (
int i =
0; i <
50; i++) {
-
if(i %
2 ==
0){
-
System.out.println(i);
-
}
-
}
-
}
-
}
- 同步方法处理继承Thread类方式的线程安全的问题:
-
//自定义一个子类继承Thread类
-
class FirstThread extends Thread{
-
//重写run方法
-
@Override
-
public void run() {
-
show();
-
}
-
-
private static synchronized void show(){
//静态方法,此时同步监视器为当前类:FirstThread.class
-
for (
int i =
0; i <
50; i++) {
-
if(i %
2 ==
0){
-
System.out.println(i);
-
}
-
}
-
}
-
}
九、线程安全的单例模式之懒汉式
- 懒汉式:默认不会实例化,什么时候用什么时候创建对象。
- 饿汉式:类加载的时候就实例化,并且创建单例对象。
懒汉式
-
public
class Lazy {
-
private
static Lazy lazy;
-
public static Lazy getInstance(){
-
//用的时候才去创建
-
if(lazy ==
null){
-
lazy =
new Lazy();
-
}
-
return lazy;
-
}
-
}
饿汉式
-
public
class Hungry{
-
//私有化构造器
-
private Hungry(){}
-
//类加载的时候就实例化,并且创建单例对象
-
private
static
final Hungry hungry=
new Hungry();
-
public static Hungry getInstance(){
-
return hungry;
-
}
-
}
- 饿汉式线程安全 :在线程还没出现之前就已经实例化了,因此饿汉式线程一定是安全的。
- 懒汉式线程不安全:因为懒汉式是用的时候才创建,这时可能会发生这种情况:线程A进入getInstance()方法发现没有lazy对象,于是准备创建,而在此时线程B也进入了getInstance()方法,也发现没有lazy对象,于是也创建了一个lazy对象,这样A、B两个线程就创建了两个不同的lazy对象,这就不满足我们的单例模式的要求,因此说线程不安全。
同步机制将懒汉式优化为线程安全的单例模式!!!
优化一:效率稍低,每个线程都需要等着前面的线程释放锁之后才能进去拿着对象出来。
-
public
class Lazy {
-
private
static Lazy lazy;
-
-
public static synchronized Lazy getInstance(){
//静态方法,此时同步监视器为当前类:Lazy.class
-
if(lazy ==
null){
-
lazy =
new Lazy();
-
}
-
return lazy;
-
}
-
}
-
public
class Lazy {
-
private
static Lazy lazy;
-
-
public static Lazy getInstance(){
-
if(lazy ==
null){
-
synchronized (Lazy.class) {
-
if(lazy ==
null)
-
lazy =
new Lazy();
-
}
-
}
-
return lazy;
-
}
-
}
十、死锁问题
-
package com.hedong;
-
-
/**
-
* @author hedong
-
* @version 1.0
-
* @date 2020/4/8 15:05
-
*/
-
public
class DeadLock {
-
public
static String a =
"a";
-
public
static String b =
"b";
-
public static void main(String[] args){
-
Thread a =
new Thread(
new MyThread1());
-
Thread b =
new Thread(
new MyThread2());
-
a.start();
-
b.start();
-
}
-
}
-
class MyThread1 implements Runnable{
-
@Override
-
public void run(){
-
try{
-
while(
true){
-
synchronized(DeadLock.a){
//同步监视器:a
-
System.out.println(
"线程一,拿到a锁");
-
Thread.sleep(
3000);
//让线程一睡眠,充当阻塞状态
-
synchronized(DeadLock.b){
//同步监视器:b
-
System.out.println(
"线程一,拿到b锁");
-
}
-
}
-
}
-
}
catch(Exception e){
-
e.printStackTrace();
-
}
-
}
-
}
-
class MyThread2 implements Runnable{
-
@Override
-
public void run(){
-
try{
-
while(
true){
-
synchronized(DeadLock.b){
-
System.out.println(
"线程二,拿到b锁");
-
Thread.sleep(
3000);
//让线程二睡眠,充当阻塞状态
-
synchronized(DeadLock.a){
-
System.out.println(
"线程二,拿到a锁");
-
}
-
}
-
}
-
}
catch(Exception e){
-
e.printStackTrace();
-
}
-
}
-
}
运行结果:
十一、线程安全问题的解决——Lock锁
在前面我们使用了Synchronized同步机制来解决线程安全问题,在jdk5.0之后,新增了一种Lock锁用来解决线程安全问题。
-
class FirstThread extends Thread{
-
//实例化ReentrantLock
-
private ReentrantLock lock =
new ReentrantLock(
true);
-
//重写run方法
-
@Override
-
public void run() {
-
try{
-
//调用lock方法上锁
-
lock.lock();
-
for (
int i =
0; i <
50; i++) {
-
if(i %
2 ==
0){
-
System.out.println(i);
-
}
-
}
-
}
finally {
-
//调用unlock解锁
-
lock.unlock();
-
}
-
}
-
}
十二、Lock锁和Synchronized的比较
- 相同点:两者都是同步机制,都能解决线程安全问题。
- 不同点:synchronized在执行完相应的同步代码后会自动释放同步监视器,而Lock锁需要在同步代码之前手动上锁开启同步,在结束之后也要手动解锁结束同步。
十三、wait()、notify()、notifyAll()
- wait():一旦执行此方法,当前线程将会进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法就会唤醒一个被wait()的线程,若有多个被wait()的线程,则将唤醒优先级最高的线程。
- notifyAll():一旦执行此方法,将会唤醒所有被wait()的线程。
- 以上三个方法必须使用在同步代码块或同步方法中。
- 以上三个方法的调用者必须为同步代码块或同步方法中的同步监视器。
十四、sleep()和wait()的异同
相同点:一旦执行两个方法,都能使当前线程进入阻塞状态。
不同点:
- 两个方法声明的位置不同:sleep()是在Thread类中声明,而wait()是在Object类中声明。
- 两个方法调用的要求不同:sleep()可以在任意需要的地方调用,而wait()只能在同步代码块和同步方法中调用。
- 关于是否释放同步监视器:当两个方法都在同步代码块或同步方法中调用时,sleep()不会释放同步监视器,而wait()会释放同步监视器。
十五、用实现Callable接口的方式创建多线程
JDK5.0新增了两种线程创建方式:实现Callable接口的方式创建多线程、使用线程池。我们这里先讲实现Callable接口的方式创建多线程。
步骤:
- 定义一个子类实现Callable接口。
- 子类中重写Callable的call()方法,注意call方法有返回值。
- 在主线程中创建Callable接口实现类的对象。
- 将Callable接口实现类的对象作为参数传递给FutureTask类的含参构造器中,创建FutureTask对象。
- 将FutureTask对象作为参数传递给Thread类的含参构造器中,创建线程对象。
- 调用线程对象的start()方法。
- 如果需要call方法的返回值,可通过FutureTask对象的get()方法获取。
-
package com.hedong;
-
-
import java.util.concurrent.Callable;
-
import java.util.concurrent.ExecutionException;
-
import java.util.concurrent.FutureTask;
-
-
/**
-
* @author hedong
-
* @version 1.0
-
* @date 2020/4/8 16:46
-
*/
-
class MyThread implements Callable {
-
//重写call方法,call方法有返回值
-
@Override
-
public Object call() throws Exception {
-
//求1——10的累加和
-
int sum=
0;
-
for (
int i =
1; i <=
10; i++) {
-
sum = sum + i;
-
}
-
return sum;
-
}
-
}
-
-
public
class ThreadTest {
-
public static void main(String[] args) {
-
//创建callable接口实现类的对象
-
MyThread myThread =
new MyThread();
-
//将callable接口实现类的对象作为参数传递到FutureTask的含参构造器中,创建FutureTask的对象
-
FutureTask futureTask=
new FutureTask(myThread);
-
//将FutureTask对象作为参数传递到Thread的含参构造器中,创建线程对象,并调用线程对象的start方法
-
new Thread(futureTask).start();
-
-
try {
-
//如果需要call方法的返回值,可以通过get方法获取
-
Object sum = futureTask.get();
-
System.out.println(
"总和为:"+sum);
-
}
catch (InterruptedException e) {
-
e.printStackTrace();
-
}
catch (ExecutionException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
实现Callable接口的方式看起来很复杂,但为什么确认为实现Callable接口的方式比实现Runnable接口的方式更好呢?
- call()可以有返回值。
- call()可以抛出异常,可以让外面的操作获取异常信息。
- callable支持泛型
十六、使用线程池创建多线程
步骤:
- 自定义一个子类实现Runable接口或Callable接口。
- 创建一个指定线程数量的线程池
- 执行指定线程操作,需要提供Runable接口或Callable接口实现类对象
- 关闭线程池。
-
package com.hedong;
-
-
import java.util.concurrent.Executor;
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
-
/**
-
* @author hedong
-
* @version 1.0
-
* @date 2020/4/8 21:21
-
*/
-
class FirstThread implements Runnable{
-
@Override
-
public void run() {
-
//遍历输出0——50的偶数
-
for (
int i =
0; i <
50; i++) {
-
if(i %
2 ==
0){
-
System.out.println(i);
-
}
-
}
-
}
-
}
-
-
public
class ThreadPool {
-
public static void main(String[] args) {
-
//提供指定线程数量的线程池
-
ExecutorService service= Executors.newFixedThreadPool(
10);
-
-
//执行指定的线程操作,需要提供Runnable接口或Callable接口实现类的对象
-
service.execute(
new FirstThread());
//适合用于提供的是Runnable接口实现类的对象
-
-
//service.submit();//适合用于提供的是Callable接口实现类的对象,因为可以获取返回值
-
-
//关闭线程池
-
service.shutdown();
-
-
}
-
}
线程池的好处:
- 提高响应速度(减少了创建新线程的时间)。
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
- 便于线程管理。
转载:https://blog.csdn.net/h2503652646/article/details/105372630