Day4:
一:关于多线程概述和简单使用
1.同步代码块:创建实现子类继承Thread类,重写run方法
注意:在Java中线程调度是抢占式的
多线程的底层原理:
当线程开始执行的时候,共享变量会拷贝一份到独立的工作内存空间,并且在工作内存空间中对共享变量进行所有操作,不会读写主内存中的变量。
扩展:一个线程类,最少有两个线程(主线程,子线程)主线程就是常写的main方法,子线程就是自己定义的线程。
创建线程有两种方式:
1.1:子类继承Thread,重写run方法
1.2:子类实现Runnable,重写run方法
1.3:锁的概念:当一个线程中添加了锁,那么该锁下面的就将进入到无限等待,只有到其他线程中解开该线程的锁,线程才会停止等待,执行锁下面的代码
1.4:synchronized这个又叫悲观锁:指定的是用synchronized保证线程同步
二:高进程和线程安全
第一种:
class Synchronized1{
public static void main(String []args){
Show show=new Show();
Show show1=new Show("线程一");
show.start(); //一个线程
show1.start(); //两个线程
}
}
classs Show extends Thread{
int num=100;
@Override
public void run(){
while(true){
//这里使用this,谁创建该类对象,谁就是这个的this
synchronized(this){
if(num<1){
break;
}
else{
try{
//如果判断不是小于1,那么就睡眠一秒
Thread.sleep(1000);
}
catch(InterruptedException e){
e.printStackTrace();
}
//获取到当前前程的名字Thread.currentThread().getname
System.out.println(Thread.currentThread().getName+"抢到票"+num);
num--;
}
}
}
}
}
第二种实现Runnable:
class Test{
public static void main(String []args){
Show show=new Show();
new Thread(show,"线程一").start();
new Thread(show,"线程二").start();
}
}
class Show implements Runnable{
int num=100;
@Override
public void run(){
for(int i=0;i>100;i++){
System.out.println(Thread.currentThread.getName()+"获取到票"+i);
}
}
}
扩展:同步锁:
class Test{
public static void main(String [] args){
A a=new A("线程一");
B b=new B();
a.start();
new Thread(b,"线程二").start();
//输出:
//java是抢占式的;所以线程是抢占的,那个线程抢占到cpu,谁就开始执行
//输出的是:
//概念:执行到抢占到的线程,如果线程执行完,重新从头抢占
}
}
第一个方式添加同步锁:
class A extends Thread{
B b=new B();
int num=100;
@Override
public void run(){
while(true){
synchronized(B.class){
if(num<100){
break;
}else{
System.out.println(Thread.currentThread.getName()+"获取到票"+num);
}
num--;
}
}
}
}
第二个方式添加同步锁:
class B implements Runnable{
@Override
public void run(){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread.getName()+"获取到票"+i);
}
}
}
二关于多线程安全和高并发
1:高并发概述:就是在同一个时间点,同时访问一个线程数据
2:线程安全:就是指在同一时间线程访问同一资源,由于线程运行机制的原因,可能会出现数据污染的问题
1:可见性概述:就是指一个线程没有看到另外一个线程修改了成员变量;
1:可见性:
calss Test{
public static void main(String [] args){
Show show=new Show("线程一");
show.start();
while(true){
if(show.flag==true){
System.out.println("Test中输出的flag为:"+flag);
}
}
//输出结果
//flag是true,但是在Test方法中if中的方法,不会被执行
}
}
classs Show extends Thread{
static boolean flag=true;
@Override
public void run(){
try{
Thread.sleep(5000);
}
catch(InterruptedException e){
e.printStackTrace();
}
flag=true;
System.out.println("flag是:"+flag);
}
}
可见性运行机制:
概括:就是在独立的工作内存中对共享变量进行了修改,但是主线程没有发现。
2:有序性:
概述:就是在代码中执行的顺序不是按照从上往下来执行的,也有可能是先在方法中执行第二行中的代码在执行第一行的代码,这就是代码的重排。
class Test{
public static void main(String [] args){
Show show =new Show();
new Thread(show,"线程一").start()
}
}
class Show implements Runnable{
@Override
public void run(){
int a=10;
int b=20; //有可能是从b开始执行的,然后在执行a中的,但是不影响c的使用,但是在多线程中代码被重排,可能会对线程访问产生影响
int c=a+b;
for(int i=0;i<c;i++){
System.out.println("这是i的第"+i+"个");
}
}
}
3:原子性:
概述:就是在线程执行的时候,独立工作内存空间将共享变量拷贝到独立内存中,当线程一对共享变量进行修改,那么共享变量会重新传回到主内存中,但是,线程二还是原来从主内存拷贝过来的共享变量,那么线程二就会进行判断,如果是相同的,那么就修改,如果是不同的那么就重新往主内存中拷贝新的共享变量,在进行修改,所以,在原子性的情况下,预想的值要比实际的值要低,这就是线程的覆盖效果。
class Test{
public static void main(){
Show show=new Show();
new Thread(show,"线程一").start();
new Thread(new Runnable{
@Override
public void run(){
for(int i=0; i<100000;i++){
show.a++;
}
System.out.println("a:"+show.a);
}
}).start();
}
}
class Show implements Runnable{
static int a=0;
@Override
public void run(){
for(int i=0 ;i<100000;i++){
a++;
}
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行完毕");
}
输出的结果:可能会是等于200000,也有可能是小于200000;
解决线程中的可见性和有序性(volatile):
概述:只要在共享变量中添加上volatile,就可以避免出现线程中的可见性,和有序性
class Show implements Runnable{
static volatile int a=0;
@Override
public void run(){
for(int i=0 ;i<100000;i++){
a++;
}
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行完毕");
}
解决代码中的可见性和有序性和原子性(AtomicInteger ):
AtomicInteger :这是在int类型中的改写方式
AtomicInteger Array:跟上面integer书写方式一样,但是这是数组的方式
//这里就直接改写Show方法,Test方法上述不变
class Show implements Runnable{
//改写a的类型定义方式
static AtomicInteger a=new AtomicInteger();
@Override
public void run(){
for(int i=0 ;i<100000;i++){
//a++,因为上述中改写了a的类型定义方式,所以自增的方式也要用定义的方式的来调用;
a.getAndIncrement();
}
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行完毕");
}