并发:只能够让多个任务在逻辑上交替执行的程序设计
并行:指物理上可以同时执行
进程:一个正在运行的程序(包含程序运行的环境、程序上下文)
线程:线程是进程中的一个执行单元
主线程(Java):执行主方法的线程
单线程:就是一条一条代码的执行。
多线程(Java):Java中允许使用多线程,允许并发的执行多个线程
如何实现多线程?
Thread:用来开启多线程 * * 1.新建一个类继承Thread类 * 2.重写父类的run方法,在run方法里面写自己的逻辑 * 3.创建子类对象 * 4.开启多线程,调用子类对象的start()方法 * JVM会帮我们调用run()方法
package Demo02_创建线程方式一;
public class Demo01 {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
System.out.println("程序结束");
}
}
class MyThread extends Thread{
//run方法运行在子线程中
@Override
public void run() {
System.out.println("MyThread run()被执行了");
}
}
cpu执行程序
1.抢:谁抢到谁执行
2.分时:你一下我一下
package Demo02_创建线程方式一;
public class Demo02 {
/**
* main方法的线程如果先抢到CPU
* @param args
*/
public static void main(String[] args) {
MyThread2 thread2 = new MyThread2();
thread2.setDaemon(true);//将线程变为守护线程
thread2.start();
//开启多线程
System.out.println("程序结束");
}
}
//正常构建的线程都是前台线程
class MyThread2 extends Thread{
public void run(){
while (true) {
System.out.println("MyThread2 的run方法被执行了");
}
}
}
线程模型
(java中不分子线程和主线程的)
主线程会等待子线程执行完毕吗?
线程的方法
获取线程名称:setName和getName方法
package Demo02_线程的方法;
public class Demo01 {
public static void main(String[] args) {
//获取线程名称
//获取当前的线程对象
Thread thread = Thread.currentThread();
System.out.println("当前线程的名称:"+thread.getName());
MyThread2 myThread2 = new MyThread2();
myThread2.setName("子线程1");
myThread2.start();
}
}
class MyThread2 extends Thread{
public void run(){
System.out.println("MyThread2 的run方法被执行了");
System.out.println("MyThread2 获取线程名称 :"+getName());
}
}
睡眠方法:Thread.sleep
package Demo02_线程的方法;
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
System.out.println("我是执行程序");
//单位:毫秒
Thread.sleep(5000);
System.out.println("我结束了");
}
}
线程创建方式二:
package Demo04_线程创建方式二;
/**
* 继承Tread类 创建线程方式的缺点:
* 1.Java中只支持单继承
*
* 通过接口方式创建线程 创建线程方式二:
* 1.新建类,实现Runnable
* 2.实现run方法,编写程序逻辑
* 3.创建Thread对象,并通过构造方法传入MyRunnable的对象
* 4.开启线程
*/
public class Demo01 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("程序输入");
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("我被执行了");
}
}
模拟买票程序
使用继承的方式:缺点: 1.java只支持单继承 2.票数不共享
package Demo05_多线程创建方式的区别;
public class Demo01 {
public static void main(String[] args) {
TicketWindow t1 = new TicketWindow();
t1.start();
TicketWindow t2 = new TicketWindow();
t2.start();
TicketWindow t3 = new TicketWindow();
t3.start();
}
}
class TicketWindow extends Thread{
//票数
private int tickets = 100;
public void run(){
while (true){
//票数大于0就继续卖票
if(tickets > 0){
System.out.println("正在售卖第"+tickets--+"张票");
}
}
}
}
实现接口的方式创建线程,模拟卖票程序
package Demo05_多线程创建方式的区别;
/**
* 实现接口的方式创建线程,模拟卖票程序
* 1.解决Java单继承问题
* 2.票数共享了(多个线程共享数据)
*/
public class Demo02 {
public static void main(String[] args) {
//多个Thread对象共享了同一个runnable对象
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
t1.start();
Thread t2 = new Thread(runnable);
t2.start();
Thread t3 = new Thread(runnable);
t3.start();
}
}
class MyRunnable implements Runnable{
private int tickets = 100;
@Override
public void run() {
while (true){
if(tickets > 0){
System.out.println("在卖第"+tickets+"张卖票");
}
}
}
}
创建线程的代码写法
package Demo06_创建线程代码写法;
/**
* 创建线程方式1
* 继承
* 创建线程方式2
* 实现
*/
public class Demo01 {
public static void main(String[] args) {
//匿名内部类(接口)
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("我是线程1");
}
};
t1.start();
//匿名内部类(接口)
Runnable r = new Runnable(){
@Override
public void run() {
System.out.println("我是线程2");
}
};
Thread t2 = new Thread(r);
t2.start();
//写法3
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是线程3");
}
}).start();
}
}
线程安全问题
什么是线程安全问题?
代码在运行过程中,如果一个共享的变量被其他线程修改,那么对于当前线程来说就发生了线程安全问题。
单线程不会发生线程安全问题。如果变量只被访问,不修改,不会发生线程安全问题。
解决线程安全问题:让可能发生线程安全的代码在某个时刻只允许一个线程访问。
即,线程同步
package demo07_线程同步;
/**
* 实现接口的方式创建线程,模拟卖票程序
* 1.解决Java单继承问题
* 2.票数共享了(多个线程共享数据(票数))
* 线程同步
* 同步代码块
* synchronized (被锁对象){
* //可能出现线程安全的代码
* }
*/
public class Demo01 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
t1.start();
Thread t2 = new Thread(runnable);
t2.start();
Thread t3 = new Thread(runnable);
t3.start();
}
}
class MyRunnable implements Runnable{
//票数
private int tickets = 100;
//锁对象可以是任意类型的对象
//但是多个线程的锁对象必须是同一个
Object obj = new Object();
public void run(){
while (true){
synchronized (obj){
//票数大于0就继续卖票
if(tickets > 0){
try {
//让问题更容易显现
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在售卖第"+tickets--+"张票");
}else{
break;
}
}
}
}
}
同步方法优化卖票程序
测试类:
package demo08_同步方法;
/**
* 整个方法的代码都会出现线程安全问题,那么可以使用同步方法
*
* 同步方法
* public synchronized void run(){}
*/
public class Demo {
public static void main(String[] args) {
//多个Thread对象共享了同一个MyRunnable对象。
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
t1.start();
Thread t2 = new Thread(runnable);
t2.start();
Thread t3 = new Thread(runnable);
t3.start();
}
}
class MyRunnable implements Runnable {
TicketsPool pool = new TicketsPool();
public void run(){
while (true){
int ticket = pool.getTicket();
if(ticket != 0){
System.out.println("正在售卖第"+ticket+"张票");
}else{
break;
}
}
}
}
TicketsPool类
package demo08_同步方法;
public class TicketsPool {
//票数
private int tickets = 100;
//获取票的方法
public synchronized int getTicket() {
try {
if(tickets > 0){
Thread.sleep(1);
return tickets--;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return 0;
}
}
同步代码块
synchronized (锁对象){ //可能出现线程安全问题的代码 } //释放锁:同步代码块执行完毕
同步方法
public synchronized void run(){ } //同步方法的锁对象:this //释放锁:同步方法执行完毕
Look锁
程序员掌控上锁和解锁的时机
package demo09_lock对象;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* lock锁
*/
public class Demo01 {
public static void main(String[] args) {
//多个Thread对象共享了同一个MyRunnable对象
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
t1.start();
Thread t2 = new Thread(runnable);
t2.start();
Thread t3 = new Thread(runnable);
t3.start();
}
}
class MyRunnable implements Runnable{
//票数
private int tickets = 100;
//创建Lock对象
Lock lock = new ReentrantLock();
public void run(){
while (true){
//上锁
lock.lock();
try{
if(tickets > 0){
try {
//让问题更容易显现
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在售卖第"+tickets--+"张票");
}else{
break;
}
}finally {
//无论如何都会释放锁
lock.unlock();
}
}
}
}
线程安全问题回顾
StringBuffer:线程同步的,线程安全的,效率低 StringBuilder:线程不同步,线程不安全,效率高 ArrayList:线程不同步 使用多线程操作StringBuilder 使用多个线程,调用同一个StringBuilder的append方法,添加数据 每个线程添加100个字符 总共300个字符 使用StringBuilder和ArrayList,因为这两线程不同步 可能导致的问题:字符丢失(不一定会丢失反正可能会有一些奇奇怪怪的问题)
package demo10_线程安全问题回顾;
public class Demo {
public static void main(String[] args) throws InterruptedException {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
//线程1
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
sb.append("A");
}
System.out.println("添加完成:"+sb.length());
}
}).start();
}
Thread.sleep(5000);
System.out.println(sb.length());
}
}
同步细节问题
如果线程1访问了append方法,那么线程2还能访问delete方法吗?
不能,因为synchronized方法使用了java类的内置锁,即锁住的是方法所属对象本身,同一个锁某个时刻只能被一个执行线程所获取,因此其他线程都得等待锁的释放。
死锁
package demo11_死锁;
/**
* 死锁:程序卡住,不会往下继续执行,也不会停止
*/
public class Demo01 extends Thread{
int flag = 1;
//锁对象
static Object obj1 = new Object();
static Object obj2 = new Object();
public static void main(String[] args) {
Demo01 d1 = new Demo01();
d1.flag = 1;
Demo01 d2 = new Demo01();
d2.flag = 2;
d1.start();
d2.start();
}
public void run(){
System.out.println("我被执行了");
if(flag == 1){
synchronized (obj1){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果obj2被锁住了,这里会继续等待
synchronized (obj2){
System.out.println("执行了线程1");
}
}
}
if(flag == 2){
synchronized (obj2){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("执行了线程2");
}
}
}
}
}
线程通信
包子铺
进货 setxxx
卖出 getxxx
生产包子的线程
不断(while(true))生产包子,添加到包子铺
消费包子的线程
从包子铺中不断(while(true))取包子
协调生产包子和消费包子,包子没有生产好,那么消费者会取不到包子
如果没有包子,让消费者等待,等包子生产好再通知消费者
包子铺类
package demo12_线程通信;
import java.util.ArrayList;
/**
* 包子铺
* 进货 setxxx
* 卖出 getxxx
*/
public class BreadStore {
//存储包子
ArrayList<String> breads = new ArrayList<>();
/**
* 设置包子
*/
public synchronized void setBreads(String bread) {
breads.add(bread);
//添加包子后,唤醒等待的线程
notifyAll();
}
/**
* 获取包子
*/
public synchronized String getBreads(){
if(breads.isEmpty()){
try {
//如果包子没有好,让调用者等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String bread = breads.get(0);
breads.remove(bread);
return bread;
}
}
get类
package demo12_线程通信;
/**
* 获取包子
*/
public class GetThread extends Thread{
BreadStore bs;
public GetThread(BreadStore bs){
this.bs = bs;
}
@Override
public void run() {
while (true){
System.out.println("获取到的包子:"+bs.getBreads());
}
}
}
set类
package demo12_线程通信;
/**
* 不断生产包子
*/
public class SetThread extends Thread{
BreadStore bs;
public SetThread(BreadStore bs){
this.bs = bs;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
bs.setBreads("a");
}
}
}
测视类
package demo12_线程通信;
/**
* 测试类
*/
public class Demo {
public static void main(String[] args) {
BreadStore bs = new BreadStore();
//启动线程
SetThread set = new SetThread(bs);
set.start();
GetThread get = new GetThread(bs);
get.start();
//如果生产包子比较慢
//有时候取到空的包子
}
}
线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
- 等待唤醒机制所涉及到的方法:
- wait():等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中
- notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的
- notifyAll():唤醒全部:可以将线程池中的所有wait()线程都唤醒。
以上方法都定义在Object类中
其实,所谓唤醒的意思就是让线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。