目录
程序,进程,线程
创建线程
继承Thread类
实现Runnable接口
Thread类中方法
线程优先级
线程状态
多线程的概念
线程同步
在Java代码中实现同步
以下代码使用继承Thread方式实现
以下代码使用实现Runnable方式实现
Lock(锁)
线程通信
案例一:两个线程交替打印1-100之间的数字
案例二:生产者/消费者问题
新增创建线程方式
程序,进程,线程
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码.
进程((process)正在内存中运行的应用程序,如运行中的QQ,运行中的音乐播放器。进程是操
作系统进行资源分配的最小单位.
线程(thread)进程可进一步细化为线程,是一个进程内部的最小执行单元,是操作系统进行调度
的最小单元,隶属于进程.
进程和线程的关系:
一个进程可以包含多个线程
一个线程只能属于一个进程,线程不能脱离进程而独立运行
每一个进程最少包含一个线程(主线程)
主线程可以创建并运行其他线程
一个进程中所有的线程共享该进程的内存资源
创建线程
创建线程的方式有俩种:
1.继承Thread类
2.实现Runnable接口
继承Thread类
Thread类中的run方法本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行 run方法。
在Java中要实现线程,可以继承Thread类,重写其中的run方法。
/*
Java中创建线程方式1
创建一个类继承java.lang.Thread
重写run()方法
*/
public class MyThread extends Thread{
/*
线程中要执行的任务都要写在run()方法中,或者在run()中调用
*/
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("MyThread"+i);
}
}
}
以下是测试代码:
public class TestThread {
public static void main(String[] args) {
MyThread myThread=new MyThread();
//myThread.run(); 这不是启动线程,知识方法调用,还是单线程模式
myThread.start();//启动线程
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
}
/*
根据代码运行情况,其中main+i和MyThread+i交错执行,说明程序并非单线程模式
*/
}
}
实现Runnable接口
java.lang.Runnable接口中仅仅只有一个抽象方法: public void run()
/*
java中创建线程的方式2
创建一个类继承Runnable接口
需要完成的线程任务加入到run方法中
优点:
1.java是但继承类,继承了一个类就不能继承另一个类,这样做可以避免单继承的局限
2.适合多线程来处理同一份资源
*/
public class TaskThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("任务线程"+i);
}
}
}
以下是测试代码:
public class TestTask {
public static void main(String[] args) {
//创建任务
TaskThread taskThread=new TaskThread();
//创建线程
Thread thread=new Thread(taskThread);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
}
}
}
实现Runnable接口的好处:
1.避免了单继承的局限性
2.多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
Thread类中方法
public class MyThread extends Thread{
/*
Thread类中的方法:
run() 用来定义线程中要执行的任务的代码
start() 启动线程
currentThread() 获取到当前线程
getId() 获取线程Id
setName() 设置线程名字
getName() 获取线程名字
setPriority(10) 设置线程优先级 为1-10(1是最低,10是最高) 默认为5 作用是为操作系统调度算法提供的
getPriority() 获取线程优先级
getState() 获取线程当前状态
sleep() 让线程阻塞休眠指定的时间 单位是毫秒
join() 等待调用了join()方法的线程执行完毕,才能执行其他线程
yield() 主动礼让 退出CPU
*/
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread());
System.out.println(Thread.currentThread().getId());
System.out.println(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getPriority());
System.out.println(Thread.currentThread().getState());
System.out.println(Thread.currentThread().getId());
try {
Thread.sleep(2000);//让线程阻塞休眠指定的时间 单位是毫秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
以下是测试代码:
public class TestMy {
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.start();
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread());
}
}
}
线程优先级
优先级较高的线程有更多获得CPU的机会,反之亦然。
优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通
过setPriority和getPriority方法来设置或返回优先级。
调度策略:
1.时间片
2.抢占式:高优先级的线程抢占CPU
Java的调度方法:
1.同优先级线程组成先进先出队列,使用时间片策略
2.对高优先级,使用优先调度的抢占式策略
Thread类有如下3个静态常量来表示优先级:
MAX_PRIORITY:取值为10,表示最高优先级。
MIN_PRIORITY:取值为1,表示最底优先级。
NORM_PRIORITY:取值为5,表示默认的优先级。
线程状态
线程的状态:
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了
运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执
行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
多线程的概念
多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不
同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
什么情况下需要多线程:
1.程序需要同时执行两个或多个任务
2. 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等等
多线程的优点:
1.提高程序的响应
2. 提高CPU的利用率
3.改善程序结构,将复杂任务分为多个线程,独立运行
多线程的缺点
1.线程也是程序,所以线程需要占用内存,线程越多占用内存也越多
2.多线程需要协调和管理,所以需要跟踪管理线程,使得cpu开销变大
3.线程之间同时对共享资源的访问会相互影响,如果不加以控制会导致数据出错
线程同步
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间
要有先来后到;
同步就是排队+锁:
几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个线程获取了这把
锁,才有权利访问该共享资源。
同步锁:
同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用来充当锁标记).
同步执行过程:
1.第一个线程访问,锁定同步对象,执行其中代码.
2.第二个线程访问,发现同步对象被锁定,无法访问.
3.第一个线程访问完毕,解锁同步对象.
4.第二个线程访问,发现同步对象没有锁,然后锁定并访问.
在Java代码中实现同步
以下实现基于模拟卖票的案例
以下代码使用继承Thread方式实现
第一种:使用synchronized(同步锁)关键字同步方法或代码块。
public class TicketThread extends Thread{
static int number=10;//模拟有十张票
static Object o=new Object();//同步锁对象
/*
synchronized(同步锁对象){
同步代码块
}
同步锁对象作用:用来记录有没有线程进入代码块,如果有线程进入代码块,那么其他线程不能进入代码块
直到上一个线程完成了代码块中的内容,释放锁后下一个线程才可以进入
同步锁对象要求:
可以是任意类的对象
同步锁对象是唯一的(多个线程拿到的是同一个对象)
*/
//模拟出票
@Override
public void run() {
while(true){
synchronized (o){
if (number >0) {
System.out.println(Thread.currentThread().getName()+"获得了第"+number+"张票");
number--;
}else {
break;
}
}
}
}
}
第二种:synchronized还可以放在方法声明中,表示整个方法为同步方法。
public class SynchronizedDemo extends Thread{
static int num = 10;//模拟有10张票
Object o =new Object();
@Override
public void run() {
while (true){
if (num <= 0) {
break;
}
print();
}
}
/*
synchronized修饰方法时,同步锁对象不需要我们指定
同步锁对象会默认提供:
1.非静态的方法--锁对象默认是this
2.静态方法--锁对象是当前类的Class对象(类的对象,一个类的对象只有一个)
*/
public static synchronized void print(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
num--;
}
}
}
以下代码为测试代码:
public class TicketTest {
public static void main(String[] args) {
TicketThread ticket=new TicketThread();
ticket.setName("窗口一");
ticket.start();
TicketThread ticket1=new TicketThread();
ticket1.setName("窗口二");
ticket1.start();
}
}
以下代码使用实现Runnable方式实现
public class TicketTask implements Runnable{
int number=10;
Object o=new Object();
public void run() {
while(true){
synchronized (o){
if (number >0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"获得了第"+number+"张票");
number--;
}else {
break;
}
}
}
}
}
以下为测试代码:
public class TicketTest {
public static void main(String[] args) {
TicketTask task=new TicketTask();
Thread thread=new Thread(task);
thread.setName("窗口一");
thread.start();
Thread thread1=new Thread(task);
thread1.setName("窗口二");
thread1.start();
}
}
Lock(锁)
从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象来实现同步。同
步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安
全的控制中,可以显式加锁释放锁
public class TicketThread extends Thread{
static int number=10;
static ReentrantLock reentrantLock=new ReentrantLock();
public void run() {
while (true) {
try {
reentrantLock.lock();//0——1 加锁
if (number > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "获得了第" + number + "张票");
number--;
} else {
break;
}
}finally {
reentrantLock.unlock();//1——0 释放锁
}
}
}
/*
synchronized 和 ReentrantLock区别:
synchronized是一个关键字,控制依靠底层编译后的执令去实现
synchronized可以修饰一个方法,还可以修饰一个代码块
synchronized是隐式的加锁和释放锁,一旦方法或代码块中运行结束或出现异常,会自动释放锁
ReentrantLock是一个类,是依靠java代码去控制(底层有一个同步队列)
ReentrantLock只能修饰代码块
ReentrantLock需要手动的加锁,手动的释放锁,所以释放锁最好写在finally中,一旦出现异常,包证锁能释放
*/
}
以下是测试代码:
public class TicketTest {
public static void main(String[] args) {
TicketTask task=new TicketTask();
Thread thread=new Thread(task);
thread.setName("窗口一");
thread.start();
Thread thread1=new Thread(task);
thread1.setName("窗口二");
thread1.start();
}
}
线程通信
线程通信指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。
涉及三个方法:
wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步锁对象。
notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高
的那个。
notifyAll一旦执行此方法,就会唤醒所有被wait的线程。
注意:
wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
案例一:两个线程交替打印1-100之间的数字
public class TicketThread extends Thread {
static int number = 1;//模拟有十张票
static Object o = new Object();//同步锁对象
@Override
public void run() {
while (true) {
synchronized (o) {
o.notify();//唤醒等待的线程
if (number <100) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
} else {
break;
}
try {
o.wait();//让线程等待,会制动释放锁 notify(),wait()必须在同步代码块中使用,必须使用锁对象调用
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
com.ffyc.javathread.threaddemo5.TicketThread ticket=new com.ffyc.javathread.threaddemo5.TicketThread();
ticket.setName("窗口一");
ticket.start();
com.ffyc.javathread.threaddemo5.TicketThread ticket1=new com.ffyc.javathread.threaddemo5.TicketThread();
ticket1.setName("窗口二");
ticket1.start();
}
}
案例二:生产者/消费者问题
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能 再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待.
柜台
public class Counter {
int num=1;
//生产者线程
public synchronized void add() throws InterruptedException {//锁对象是this
if (num==0){
this.notify();
num=1;
System.out.println("生产者生产了一件商品");
}else {
this.wait();
}
}
//消费者线程
public synchronized void sub() throws InterruptedException {//锁对象是this
if (num>0){
this.notify();
num=0;
System.out.println("消费者取走了一件商品");
}else{
this.wait();
}
}
/*
sleep() 和 wait() 的区别
sleep():
Thread中的方法
sleep()方法经过设定的时间后会自动唤醒
sleep()方法不会释放锁
wait():
锁对象中的方法
wait后的线程,必须通过其他线程的唤醒(notify() / notifyAll() )
wait方法会自动释放锁
*/
}
消费者
/*
消费者
*/
public class Consumer extends Thread{
Counter counter;
public Consumer(Counter counter) {
this.counter=counter;
}
@Override
public void run() {
while(true){
try {
counter.sub();
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
生产者
public class Producers extends Thread{
Counter counter;
public Producers(Counter counter) {
this.counter=counter;
}
@Override
public void run() {
while(true){
try {
counter.add();
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
以下是测试代码:
public class Test {
public static void main(String[] args) {
Counter counter= new Counter();
Producers producers=new Producers(counter);
producers.start();
Consumer consumer=new Consumer(counter);
consumer.start();
}
}
新增创建线程方式
实现Callable接口与使用Runnable相比,Callable功能更强大些,其具有以下特性:
1.相比run()方法,可以有返回值
2.方法可以抛出异常
3.支持泛型的返回值
4.需要借助FutureTask类,获取返回结果
5.支持泛型
public class SumTask <T>implements Callable<T> {
@Override
public T call() throws Exception {
Integer n=0;
for (int i = 0; i < 10; i++) {
n+=i;
}
return (T)n;
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
SumTask<Integer> sum =new SumTask<Integer>();
FutureTask<Integer> future=new FutureTask<Integer>(sum);
Thread thread=new Thread(future);
thread.start();
Integer sumber=future.get();//获取call方法返回的执行结果
System.out.println(sumber);
}
}