线程的基本概念
基本的进程线程概念
进程:是操作系统中进行保护和资源分配的基本单位,操作系统分配资源以进程为基本单位。
线程:是进程的组成部分,它代表了一条顺序的执行流。
线程:另一种解释是从一个main方法进入栈 ,到main方法出栈 栈中没有任何进程 ,这叫一个线程 。
栈帧 :栈中的方法称为栈帧,其都是拷贝过来的,而不是直接拿过来用的。
下面以我的理解,简单说说JVM机制
栈
存放地址信息和与执行顺序相关的信息 ,分为 主栈和子栈
(main方法 是 外边调用的 线程 main方法自己也有一个线程栈)
堆
在堆中的信息 如果是基本类型 则申请固定大小的空间给他
如果是引用类型 则申请他的地址大小的空间,仅仅存放地址 指向它的空间
(栈中存放的信息是与执行顺序相关的 而 堆中 存放的是与顺序无关的信息)
方法区
存放 方法 和 类信息 包括成员和方法 相当于一个蓝图,图纸 实例化相当于按照图纸 制造东西
接下来接着说多线程
多线程是并发执行的
多线程的运行是在一段时间内同时运行 ,他们竞争cpu ,通过时间片的原则运行
两个线程 各自都有一个线程栈 他们如果都调用run 方法
他们都会拷贝一份run方法,以及run方法内部的变量回去,放到栈中
当对 共同的变量进行操作的时候 会产生 线程不安全的问题, 而产生脏数据
public class ThreadDemo extends Thread{
private String name;
private Tree tree;
ThreadDemo(String name, Tree tree){
this.name = name;
this.tree = tree;
}
public void run() {
ZhanTest bn = new ZhanTest();
for(int i = 0; i < 10000; i++) {
tree.value += 1;
}
}
}
public class Test {
//实例化两个线程 让他们都读写tree里边的数据
public static void main(String[] args) {
Tree tree = new Tree();
tree.value = 10;
int a = 0;
ThreadDemo t1 = new ThreadDemo("aaa",tree);
t1.start();
ThreadDemo t2 = new ThreadDemo("bbb",tree);
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(tree.value);
}
由于读取和写入没有任何限制 所以产生了脏数据。
而这引出了 锁的概念
锁的概念
锁放在方法上 但是实际锁住的是方法中的资源(共享变量的时候加锁)
实际锁就是线程之间的约定 例如
每次读写不仅读入数据还要读取当前的版本号,然后回去进行操作,当进行写操作的时候,如果自身版本号对不上 则读取新的数据和版本号 ;回去继续操作;
(实际中的锁比这些要版本之类的复杂的多)
(对run加锁是锁不住的,对象的引用类型是锁不住,只能锁住基本类型的数据)
数据:1
锁:A或B // 实现可读入还是等待的控制
版本号:v_1
对象锁:
如果有两个非静态方法 并且都加锁 , 会对所有非静态的加锁
(第一个执行完,第二个才能执行)
伪代码如下:
public synchronized void a(){
print("a1");
Thread.sleep(1000);
print("a2");
}
public synchronized void b(){
print("b1");
Thread.sleep(1000);
print("b2");
}
输出:a1a2b1b2
类锁
类锁是锁住整个类,当有多个线程来声明这个类的对象时候将会被阻塞,直到拥有这个类锁的对象呗销毁或者主动释放了类锁,这个时候在被阻塞的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住
不管多少个对象,共用一把锁,且只有一把,不管怎么调用,都会同步
上面方法加static变类锁:
静态的锁 是类锁,这个类中锁共享, 其在方法区中
如果两个静态的方法都加锁,也会互相锁定。
public static synchronized void a(){
print("a1");
Thread.sleep(1000);
print("a2");
}
public static synchronized void b(){
print(" b1");
Thread.sleep(1000);
print("b2");
}
输出结果是 a1a2b1b2
加在静态方法的锁属于类锁 ,在方法区,而加在非静态方法的锁是对象锁,在堆中
锁,只在该类中起作用,即内存中的统一资源,在不同的类中不起作用
在编程时候 对锁的资源的读和写要在一个方法中
加在非静态类的方法的锁 锁不住静态的资源
但是,当静态的资源在非静态的加锁的方法中的时,调用该方法的线程需要排队去访问这个静态的资源
public class DataTest {
//静态的属于类,非静态的属于对象
private static int ticket = 100;//静态资源
public synchronized void method1() {
System.out.println("俺是方法1--1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("俺是方法1---2");
}
public synchronized void method2() {
System.out.println("俺是方法2---1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("俺是方法2---2");
}
public synchronized void method3(String name,FlagTest f) {
if( ticket == 0) {
f.flag = 1;
System.out.println( "卖完了" );
return;
}
System.out.println(name + "卖出了第" + ticket);
ticket = ticket - 1;
}
}
public class ThreadTest extends Thread{
private DataTest data;
private int flag;
private String name;
ThreadTest(DataTest data, int flag, String name){
this.data = data;
this.flag = flag;
this.name = name;
}
public void run() {
FlagTest f = new FlagTest();
while(f.flag == 0) {
data.method3(name,f);
}
}
}
public class FlagTest {
public int flag = 0;
}
public class Test {
public static void main(String[] args) {
DataTest test = new DataTest();
ThreadTest t1 = new ThreadTest(test,1,"张三");
ThreadTest t2 = new ThreadTest(test,1,"李四");
ThreadTest t3 = new ThreadTest(test,1,"王五");
t1.start();
t2.start();
t3.start();
}
}
在new了每个线程之后 在各自个线程栈中 会压入各自的 run 方法 而他们要操作的资源在一个类中
所以对 那个类中的方法加锁 就可以控制线程对这个资源的读取 从而形成竞争 和 阻塞
在一个对象中 ,如果有个方法有锁,则将这个对象就锁住了。
例如:
如果一个 非静态的加锁的方法 去访问这个对象的 非静态的资源 int aaa;则锁住的这个对象
如果又有一个 非静态的加锁的方法 再去访问这个对象的 非静态的资源 int fff ; 则访问不了进入阻塞
如果是 非静态的不加锁的方法 访问 非静态的资源 int fff 则可以访问;
多线程 并行 并发概念
并行: 线程的资源不竞争 即可并行
并发: 线程之前存在竞争资源 即是并发
Java中的 HashMap 是线程不安全的
加锁与不加锁的区别(不加锁的运行快 ,加锁的运行慢)