线程基础知识
本文主要是对线程的概念及用法做一个基础介绍
进程:内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少一个线程(线程实际上是在进程的基础上进一步划分,一个进程启动之后,里面的若干执行路径又可以分划分成若干个线程)
需要明白一个线程调度的概念:
1、分时调度:所有线程轮流使用CPU使用权,平均分配每个线程占用CPU的时间
2、抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同(线程随机性),而java使用抢占式调度
一、线程使用(三种方式)
1、继承Thread类
1、继承 Thread 线程类
2、重写run方法(线程要执行的任务方法)
3、实例MyThread 后,调用start方法来启动任务执行run方法
代码如下(示例):
public class MyThread extends Thread{
@Override
public void run(){
System.out.println("锄禾日当午");
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread run =new MyThread();
run.start();
}
}
2、Runnable接口实现
1、定义MyRunnable 类实现接口Runnable
2、重写run方法
3、实例MyRunnable,
4、实例Thread,并将实例MyRunnable对象传入其中
5、线程对象调用start方法
代码如下(示例):
public class MyRunnable implements Runnable{
@override
public void run(){
System.out.println("锄禾日当午");
}
}
public class ThreadTest {
public static void main(String[] args) {
//执行还是需要使用Thread
//创建一个任务对象
MyRunnable r= new MyRunnable();
//MyRunnable r= new MyRunnable(name);//name 线程名称
//创建一个线程,并分配一个任务
Thread t =new Thread(r);
t.start();
}
}
3、带返回值的线程Callable
1、编写类,实现接口Callable
2、创建FutureTask对象,并传入第一步创建的Callable对象
3、通过Thread,启动线程
代码如下(示例):
public class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子线程执行..");
Thread.sleep(3000)
return 100;
}
}
public class CallableTest {
public static void main(String[] args) {
//1、编写类,实现接口Callable
//2、创建FutureTask对象,并传入第一步创建的Callable对象
//3、通过Thread,启动线程
Callable<Integer> mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread t = new Thread(ft);
t.start();
//如果任务ft调用了get方法, 主线程会等到拿到子线程的返回值之后再继续执行
try {
Integer m = ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//3秒之后再输出
System.out.println("主线程执行..");
}
}
二.线程中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定
代码如下(示例):
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
//在进行部分特殊的操作时,会检查中断标记,发现之后,就会进入catch块
//Object.wait(), Object.wait(long), Object.wait(long, int),
// Thread.sleep(long), Thread.interrupt(), Thread.interrupted()
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("子线程结束");
return;
}
}
}
}
public static void main(Stirng[] arg){
//创建一个任务对象
MyRunnable r= new MyRunnable();
//创建一个线程,并分配一个任务
Thread t =new Thread(r);
//执行
t.start();
//主线程循环
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//给线程t添加中断标记--》这里是主线程执行完毕,给子线程打标记后,
//子线程再执行就会进入catch语句中,catch语句中return,子线程自动结束;
t.interrupt()
}
输出结果:
Thread-0:1
main:1
Thread-0:2
main:2
main:3
Thread-0:3
Thread-0:4
main:4
Thread-0:5
子线程结束
三、线程安全问题
线程不安全:多个线程同时运行时,容易发生数据紊乱的问题
假设:count值为1,线程1执行到sleep后,失去时间片,此时count还是1,线程2或者线程3争抢执行,进入while循环,造成的结果就是count小于0
代码如下(示例):
public class ThreadTest {
public static void main(String[] args) {
//创建三个线程,执行同一个卖票任务
Runnable run =new MyRunnable();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
//结果,count有可能小于0
}
}
cpublic lass MyRunnable implements Runnable{
private int count = 10
@Override
public void run() {
while (count>0){
System.out.println("正在卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
}
}
}
结果:
正在卖票
出票成功,余票:4
正在卖票
出票成功,余票:3
正在卖票
出票成功,余票:2
正在卖票
出票成功,余票:1
正在卖票
出票成功,余票:0
出票成功,余票:-1
出票成功,余票:-2
解决方案有三种
1、方法1–同步代码块
代码如下(示例):
class MyRunnable implements Runnable{
private int count = 10;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){//obj-->锁对象,一个线程运行时,其余两个线程在外面方位obj是否解锁,同一时间,只有一个线程能执行if语句
if(count>0){
System.out.println("正在卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
}else{
break;
}
}
}
}
}
2、方法2–同步方法
锁的对象, 实例方法–锁的是this,当前调用对象
静态方法-锁的是类名.class 字节码class文件对象
多个同步方法存在且锁定同一个this时,只能有一个方法执行,其余都执行不了,(同一把锁的原因)
代码如下(示例):
class MyRunnable implements Runnable{
private int count = 10;
private Object obj = new Object();
@Override
public void run() {
while (true){
boolean flag = sale();
if(!flag)
break;
}
}
//方法
public synchronized boolean sale(){
if(count>0){
System.out.println("正在卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
return true;
}
return false;
}
}
3、方法3–显示锁 Lock -->使用子类 ReentrantLock
代码如下(示例):
class MyRunnable implements Runnable{
private int count = 10;
//实例一个锁对象
private Lock l= new ReentrantLock ();
@Override
public void run() {
while (true){
l.lock();//上锁
if(count>0){
System.out.println("正在卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
}else{
break;
}
}
l.unlock();//解锁
}
}
四、多线程通信
Object 方法
wait():导出当前线程等待被唤醒,可以传入具体的时候,
notify():唤醒正在此对象监视器上等待的单个线程
notifyAll():唤醒正在此对象监视器上等待的所有线程
代码如下(示例):
//多线程通信问题--生产者与消费者
//厨师产生一份食物,服务员取走一份,交替执行
public class ManyThreadNews {
public static void main(String[] args) {
//穿件
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
}
//厨师
class Cook extends Thread{
private Food f;
public Cool(Food f) {
this.f = f;
}
@Override
public void run() {
//循环100次做菜-->厨师线程循环100次,生成各50份的菜式
for (int i = 0; i < 100; i++) {
if(i%2==0){
f.setNameAndTaste("白面馒头","白味的");
}else{
f.setNameAndTaste("大包子","甜甜的");
}
}
}
}
//服务员
class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
f.get();//取走
}
}
}
//食物
//为什么加了同步关键字也不行?因为,方法解锁之后,同一个线程可能又抢到了时间片,继续执行(现在需要的是交替执行)
class Food{
private String name;
private String taste;
//true,厨师执行,false,服务员执行
private boolean flag = true;
//厨师生成一份,设置名称和味道
synchronized void setNameAndTaste(String name, String taste){
if(flag){
this.name = name;
//为了显示效果,此处等待100ms,此时,厨师线程有可能失去时间片,但菜名已经更新,而服务员开始执行取走任务,味道是上一次的,数据紊乱
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste =taste;
this.flag = false;
//此时还不行,还需要此线程睡眠,并唤醒服务员线程,才能交替执行
this.notifyAll();//唤醒睡眠的线程
try {
this.wait();//当前线程进入睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//服务员取走一份菜
synchronized void get(){
if(!flag){
System.out.println("服务员取走:"+this.name+",味道:"+this.taste);
this.flag = true ;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}