多线程
进程、线程与纤程
进程:进程是资源分配的基本单位,每一个进程都有一块独立的内存空间
线程:线程是独立执行的基本单位,一个进程中至少有一个线程且可以有多个线程.
纤程:纤程又叫协程,是更轻量级的线程,一个线程可以包含一个或多个纤程
线程是重量级的,纤程是轻量级的线程,它消耗小,切换快。在某些特定的情况下,纤程的效率要比线程的效率高很多
线程调度的两种方式
线程调度:
分时调度
所有线程轮流使用CPU,平均分配每个线程占用CPU的时间
抢占式调度
优先让优先级高的线程使用CPU,如果优先级相同,则随机选择一个,JAVA使 用的是抢占式调度
线程的五种状态
新建(NEW):新创建了一个线程对象
可运行(RUNNABLE):被调用start(),该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权
运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。
死亡(DEAD):线程的生命周期结束
同步与异步
同步:排队执行任务,效率较低,安全性高
异步:同时执行任务,效率较高,安全性较低
并发与并行
并发:指两个或多个事情在同一时间段内发生
并行:指两个或多个事情在同一时刻发生
JAVA实现线程的三种方式
1.继承Thread类
创建自己的线程类然后继承Thread类,重写run方法,run方法中是线程所要执行的任务,启动线程调用Thread类中的start()方法
public class test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
static class MyThread extends Thread{
@Override
public void run(){
System.out.println("执行线程中的任务");
}
}
}
2.实现Runnable接口
创建一任务类,实现Runnable接口,重新run方法创建任务,然后创建一个线程执行该任务
public class test {
public static void main(String[] args) {
//创建自己的任务类,实现Runnable接口
Runnable r = new MyRunnable();
//创建一个线程,执行任务
Thread t = new Thread(r);
t.start();
}
static class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println("执行线程中的任务");
}
}
}
如果任务只使用一次可以用匿名内部类的方式实现
public class test {
public static void main(String[] args) {
//创建一个线程,使用匿名内部类的方式实现任务
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("要执行的任务");
}
});
t.start();
}
}
利用jdk8中的Lambda表达式实现
public class test {
public static void main(String[] args) {
//创建一个线程,使用Lambada表达式实现
Thread t = new Thread(()->{System.out.println("要执行的任务");});
t.start();
}
}
3.实现Callable接口
实现Callable接口可以实现一个有返回值的任务
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask<>(myCallable);
Thread t = new Thread(futureTask);
t.start();
//获取返回值
String result = (String)futureTask.get();
System.out.println(result);
}
static class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("要执行的任务");
return "线程的返回值";
}
}
}
线程安全问题
当多个线程操作同一个资源时就会出现线程安全问题,比如三个窗口卖共10张票,则这三个窗口共同去操作一个变量:票数,在第一个窗口判断还有余票后进行买票,在第一个窗口出票时候第二个窗口判断是否还有余票,此时第一个窗口正在出票过程中还未出票完成,此时票并未被卖出,则第二个窗口判断还有余票也进行卖票,当两个窗口都买票结束时,其实他们卖出的是同一张票.
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Runnable runnable = new MyCallable();
new Thread(runnable,"窗口1").start();
new Thread(runnable,"窗口2").start();
new Thread(runnable,"窗口3").start();
}
static class MyCallable implements Runnable{
private int ticket = 10;
@Override
public void run(){
while (true){
if(ticket>0){
try {
//为提高出错几率,买票前休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出买票结果
System.out.println(Thread.currentThread().getName()+"卖票,剩余票数:"+( ticket--));
}else {
break;
}
}
}
}
}
解决多线程安全问题
1.利用synchronized关键字解决
同步代码块,被synchronized关键字修饰的代码块被称为同步代码块,将任务中会出现线程安全的代码放到同步代码块中,同步代码块需要传入一个锁对象,且所有线程必须操作同一锁对象才可以保证线程安全
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Runnable runnable = new MyCallable();
new Thread(runnable,"窗口1").start();
new Thread(runnable,"窗口2").start();
new Thread(runnable,"窗口3").start();
}
static class MyCallable implements Runnable{
private int ticket = 10;
//锁对象,该对象一个任务只有一个
Object o = new Object();
@Override
public void run(){
while (true){
//同步代码块解决线程安全
synchronized (o){
if(ticket>0){
try {
//为提高出错几率,买票前休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出买票结果
System.out.println(Thread.currentThread().getName()+"卖票,剩余票数:"+( ticket--));
}else {
break;
}
}
}
}
}
}
如果线程操作了不同的锁对象,则同步代码块无法保证
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Runnable runnable = new MyCallable();
new Thread(runnable,"窗口1").start();
new Thread(runnable,"窗口2").start();
new Thread(runnable,"窗口3").start();
}
static class MyCallable implements Runnable{
private int ticket = 10;
@Override
public void run(){
//锁对象,每次调run方法便会生成一次
Object o = new Object();
while (true){
//同步代码块解决线程安全
synchronized (o){
if(ticket>0){
try {
//为提高出错几率,买票前休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出买票结果
System.out.println(Thread.currentThread().getName()+"卖票,剩余票数:"+( ticket--));
}else {
break;
}
}
}
}
}
}
同步方法
synchronized修饰的方法被称为同步方法,当方法为静态方法是,锁对象是类名.class,当方法为非静态方法时,锁对象为this
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Runnable runnable = new MyCallable();
new Thread(runnable,"窗口1").start();
new Thread(runnable,"窗口2").start();
new Thread(runnable,"窗口3").start();
}
static class MyCallable implements Runnable{
private int ticket = 10;
@Override
public void run(){
while (true){
//同步方法块解决线程安全
boolean flag = SellingTickets();
if(!flag){
break;
}
}
}
private synchronized boolean SellingTickets(){
if(ticket>0){
try {
//为提高出错几率,买票前休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出买票结果
System.out.println(Thread.currentThread().getName()+"卖票,剩余票数:"+( ticket--));
return true;
}
return false;
}
}
}
2.Lock锁
lock锁,使用时手动获取锁和释放锁,比synchronized更加灵活;可中断的获取锁;超时获取锁。
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Runnable runnable = new MyCallable();
new Thread(runnable,"窗口1").start();
new Thread(runnable,"窗口2").start();
new Thread(runnable,"窗口3").start();
}
static class MyCallable implements Runnable{
private int ticket = 10;
Lock lock = new ReentrantLock();
@Override
public void run(){
while (true){
//上锁
lock.lock();
if(ticket>0){
try {
//为提高出错几率,买票前休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出买票结果
System.out.println(Thread.currentThread().getName()+"卖票,剩余票数:"+( ticket--));
}else {
break;
}
//释放锁
lock.unlock();
}
}
}
}