并发与并行
并发:两个或多个事件在同一时间段内发生。
并行:两个或多个事件在同一时刻发生(同时发生)。
进程与线程
●进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
●线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,-一个进程中至少有一 个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之: 一个程序运行后至少有一个进程, 一个进程中可以包含多个线程
多线程好处:效率高、多个线程之间互不影响。
线程调度
分时调度:所有线程轮流使用CPU使用权,怕平均分配每个线程占用CPU的时间。
抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,就随机选择一个(线程随机性),Java为抢占式调度。
创建多线程
继承Thread类
1、创建一个类继承Thread类
2、重写Thread中的run()方法
3、创建类的对象,再调用start方法。
一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。例如,计算大于某一规定值的质数的线程可以写成:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,下列代码会创建并启动一个线程:
PrimeThread p = new PrimeThread(143);
p.start();
//创建一个类继承Thread类
public class NewThread extends Thread{
@Override
//重写Thread中的run()方法
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("run()方法"+i);
}
}
}
public class NewMain {
public static void main(String[] args) {
//创建类的对象,再调用start方法。
NewThread newThread = new NewThread();
newThread.start();
for (int i = 0; i < 5; i++) {
System.out.println("main()方法"+i);
}
}
}
两次执行结果:
"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=61793:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.lg.thread.NewMain
main()方法0
run()方法0
main()方法1
run()方法1
main()方法2
run()方法2
main()方法3
run()方法3
main()方法4
run()方法4
Process finished with exit code 0
"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=61947:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.lg.thread.NewMain
run()方法0
run()方法1
run()方法2
run()方法3
run()方法4
main()方法0
main()方法1
main()方法2
main()方法3
main()方法4
Process finished with exit code 0
随机打印结果原理:
内存图
获取当前正在执行线程名的方法重写run()方法的过程中。
public class TestThread extends Thread {
@Override
public void run() {
//方法1
/*String str = getName();
System.out.println(str);*/
//方法2
/*Thread t = Thread.currentThread();
System.out.println(t);*/
//链式编程
System.out.println(Thread.currentThread().getName());
}
}
public class Test {
public static void main(String[] args) {
TestThread th = new TestThread();
th.start();
new TestThread().start();
new TestThread().start();
System.out.println(Thread.currentThread().getName());//mian.获取主线程名称
}
}
结果不一:
main
Thread-0
Thread-1
Thread-2
改变线程名程两种方法
public class TestThread extends Thread {
public TestThread() {
}
//创建带参数的构造方法。把线程名传递给父类,让父类Thread给线程改名
public TestThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Test {
public static void main(String[] args) {
TestThread th = new TestThread();
//使用Thread类中的setName()方法
th.setName("One");
th.start();
//创建带参数的构造方法。把线程名传递给父类,让父类Thread给线程改名
new TestThread("Two").start();
}
}
线程睡眠使得线程以多少毫秒睡眠。
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);//使得线程睡眠一秒,结果每个一秒打印一个数
System.out.println(i);
}
}
}
创建线程的第二种方法
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,下列代码会创建并启动一个线程:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
具体代码
public class Test implements Runnable {
//重写Runnable的run()方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("新线程"+i);
}
}
}
public class New {
public static void main(String[] args) {
Test test = new Test();
//开启线程
new Thread(test).start();
for (int i = 0; i < 5; i++) {
System.out.println("main线程"+i);
}
}
}
结果不一:
"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=63372:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.New
main线程0
新线程0
main线程1
新线程1
main线程2
新线程2
main线程3
新线程3
main线程4
新线程4
Process finished with exit code 0
"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=63405:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.New
main线程0
新线程0
main线程1
新线程1
新线程2
新线程3
新线程4
main线程2
main线程3
main线程4
Thread和Runnable的区别
1、避免单继承的局限性。
一个类只能继承一个类,类继承了Thread类就不能继承其他类。实现了Runnable接口,还可以继承其他类,实现其他接口。
2、增强了程序的扩展性,降低了程序的耦合性(解耦)。
实现Runnable方式,把设置线程任务和开启新线程进行了分离。(实现类中重写Run方法:用来是设置线程任务。创建Thread类调用start方法:用来开启线程)
匿名内部类方式实现线程创建
简化代码
:
1、把子类继承父类,重写父类方法,创建子类对象一步完成
2、把实现类实现接口,重写接口中的方法,创建实现类对象合成一步。
产物
:子类或实现类对象,而这个类没有名字
具体代码:
public class New {
public static void main(String[] args) {
//线程的接口Runnable
Runnable r = new Runnable(){
@Override
public void run() {
for (int s = 0; s < 2; s++) {
System.out.println(Thread.currentThread().getName()+"Runnable");
}
}
};
//线程的父类Thread
new Thread(r).start();
new Thread(){
@Override
public void run() {
for (int s = 0; s < 2; s++) {
System.out.println(Thread.currentThread().getName()+"Thread");
}
}
}.start();
//简化接口,开始套娃
new Thread(new Runnable(){
@Override
public void run() {
for (int s = 0; s < 2; s++) {
System.out.println(Thread.currentThread().getName()+"套娃");
}
}
}).start();
}
}
结果依旧不一:
"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=64545:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.New
Thread-2套娃
Thread-2套娃
Thread-1Thread
Thread-1Thread
Thread-0Runnable
Thread-0Runnable
Process finished with exit code 0
"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=64552:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.New
Thread-0Runnable
Thread-0Runnable
Thread-2套娃
Thread-2套娃
Thread-1Thread
Thread-1Thread
Process finished with exit code 0
线程安全
多个线程访问共享数据。
public class ThreadSafe implements Runnable {
private int ticket = 10;
@Override
public void run() {
while(true){
if(ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"票");
ticket--;
}
}
}
}
public class Demo {
public static void main(String[] args) {
ThreadSafe th = new ThreadSafe();
Thread th0 = new Thread(th);
Thread th1 = new Thread(th);
Thread th2 = new Thread(th);
th0.start();
th1.start();
th2.start();
}
}
结果:
线程Thread-0正在卖第10票
线程Thread-2正在卖第10票
线程Thread-1正在卖第10票
线程Thread-2正在卖第7票
线程Thread-0正在卖第7票
线程Thread-1正在卖第5票
线程Thread-0正在卖第4票
线程Thread-2正在卖第4票
线程Thread-1正在卖第2票
线程Thread-2正在卖第1票
线程Thread-0正在卖第1票
线程Thread-1正在卖第-1票
线程安全产生的原理
注意:线程安全是不能产生的,可以让一个线程在访问共享资源时,无论是否失去CPU的执行权,其他的线程只能等待,等待当前线程使用完共享资源,其他线程才可以使用资源。
解决线程安全问题:
1、同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实施互斥访问。
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为再对象上标记了一个锁
1、锁对象可以是任意类型。
2、多个线程对象,需要使用同一把锁。
在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能在外等着。
public class ThreadSafe implements Runnable {
private int ticket = 10;
Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
if(ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"票");
ticket--;
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
ThreadSafe th = new ThreadSafe();
Thread th0 = new Thread(th);
Thread th1 = new Thread(th);
Thread th2 = new Thread(th);
th0.start();
th1.start();
th2.start();
}
}
结果:
"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=59831:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.Demo
线程Thread-0正在卖第10票
线程Thread-0正在卖第9票
线程Thread-0正在卖第8票
线程Thread-0正在卖第7票
线程Thread-0正在卖第6票
线程Thread-0正在卖第5票
线程Thread-2正在卖第4票
线程Thread-1正在卖第3票
线程Thread-1正在卖第2票
线程Thread-2正在卖第1票
解析:
2、同步方法
synchronized修饰的方法叫做同步方法,保证A线程执行的时候,其他线程只能在方法外面等着。
public synchronized void method(){
可能产生线程安全的代码
}
定义同步方法,同步方法会把方法内部的代码锁住,只让一个线程执行。非静态同步方法的锁对象是:就是实现类对象new ThreadSafe();
也就是this
。静态方法的锁对象:不能是this,this是创建对象之后产生的,静态方法优先于对象,静态同步方法的锁对象是本类的class属性-->class文件属性。
//静态同步方法
public static /*synchronized */ void staticMethod()
{
synchronized (ThreadSafe.class){//《-----------同样保证线程安全
if(ticket0>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket0+"票");
ticket0--;
}
}
}
同步方法具体代码:
public class ThreadSafe implements Runnable {
private int ticket = 10;
private static int ticket0 = 10;
Object obj = new Object();
@Override
public void run() {
System.out.println("this"+this);
while(true)
{
//method();//调用非静态同步方法
staticMethod();//调用静态同步方法
}
}
//非静态同步方法
public synchronized void method()
{
synchronized (obj){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"票");
ticket--;
}
}
}
//静态同步方法
public static synchronized void staticMethod()
{
if(ticket0>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket0+"票");
ticket0--;
}
}
}
结果未出现线程安全问题:
runcom.ls.ThreadSafe@312b1dae
thiscom.ls.ThreadSafe@312b1dae
thiscom.ls.ThreadSafe@312b1dae
thiscom.ls.ThreadSafe@312b1dae
线程Thread-0正在卖第10票
线程Thread-0正在卖第9票
线程Thread-2正在卖第8票
线程Thread-2正在卖第7票
线程Thread-2正在卖第6票
线程Thread-2正在卖第5票
线程Thread-1正在卖第4票
线程Thread-2正在卖第3票
线程Thread-0正在卖第2票
线程Thread-0正L在卖第1票
3、使用Lock锁
public interface LockLock
实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。
虽然 synchronized 方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();//无论代码是否有异常,都将锁释放掉。
}
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
具体代码:
public class RunnableImpl implements Runnable {
public int ticket = 10;
//创建一个ReentrantLock对象
Lock l = new ReentrantLock();
@Override
public void run() {
while(true)
{
method();//改动后
//methodOne();//改动前
}
}
public void method(){
//在可能要出现安全问题的代码前调用lock接口中的方法lock获取锁
l.lock();
try {
Thread.sleep(100);
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//在可能要出现安全问题的代码后调用lock接口中的方法lock释放锁
l.unlock();//不管程序是否发生异常都要释放锁
}
}
public void methodOne(){
l.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
l.unlock();
}
}
public class Test {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread one = new Thread(run);
Thread two = new Thread(run);
Thread three = new Thread(run);
one.start();
two.start();
three.start();
}
}
结果如下:
"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=64755:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo03.Test
Thread-0正在卖第10张票
Thread-0正在卖第9张票
Thread-2正在卖第8张票
Thread-1正在卖第7张票
Thread-0正在卖第6张票
Thread-2正在卖第5张票
Thread-2正在卖第4张票
Thread-1正在卖第3张票
Thread-0正在卖第2张票
Thread-2正在卖第1张票
线程的状态
等待唤醒案例,线程之间的通信。
public class Test {
public static void main(String[] args) {
Object obj = new Object();
new Thread(){
@Override
public void run() {
System.out.println("告知包子数量");
synchronized (obj){
//此处异常不能抛出
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子做好了,开吃!");
}
}
}.start();
new Thread(){
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("包子做好了");
obj.notify();
}
}
}.start();
}
}
本人意在记录自己的学习,并无他心