======================================================================
======================================================================
======================================================================
java第十四天
======================================================================
(1) 线程的同步
进程是一套顺序执行的指令。
同时开辟并行执行序列即多线程 run() 结束,线程就结束
JVM就是一个进程,在JVM中分出线程
进程数据空间独立;线程数据空间共享, 线程间通信更容易
分配的方式不如线程好,能充分使用CPU的资源。 共享数据就要加锁、解锁,会降低效率。
OS有 时分共享操作系统 和 实时操作系统
时分共享操作系统 时间片 调度系统把每个时间片分给多个进程 性能调优是根据时间片划分, 时间片会影响整个操作系统。
为了让某些工作并行,在进程主线程中开辟多个线程,这就是线程的机制。
我们关心的是被线程共享的数据,主要看synchronized加在何处。往往不加在线程里面,而是在共享的对象上。
只有运行状态的线程才有机会执行代码!
只有等到所有线程终止,进程才结束!
当一个对象分配给某线程锁标记时,其它线程不能访问同步方法,但能访问非同步方法!
起线程的两种方式:1、继承Thread,覆盖run()方法,用start()启动
2、实现Runnable接口,用来构造线程对象。(线程对象不是线程,但代表一个线程)
当多线程并发访问同一个对象(临界资源)的时候,如果原子操作被打破,则会造成数据的不一致.
在java中,用给临界资源加锁的机制来解决这个问题.
每一个对象都有一个互斥锁标记(monitor),只能分配给一个线程(对象相当于女孩,每个女孩都有一个锁标记,并且这个锁标记只能给一个男孩(线程)).
synchronized(o[对象的引用]){对O对象加锁的同步代码块}
synchronized可以作为一个方法的修饰符,对当前对象加锁.
例子:
public synchronized void m(){}
public void m(){
synchronized(this){}//对当前对象的方法加锁.
}
每一个对象都有一个锁池,它里面装的是等待该对象锁标记的线程.(就象每个女孩都有一个锁池,里面放着等待要这个女孩锁标记的男孩(线程))
一个线程可以拥有多个对象的标记.同步代码块是可以嵌套的(一个男孩可以有多个女孩的锁标记,但是一个女孩只能有一个男朋友)换句话说就是(一个线程可以拿到多个对象的锁标记,但是一个对象的锁标记只能给一个线程。).
synchronized(o1){
synchronized(o2){ //一个线程可以拥有多个对象的锁标记.
t1;
}
}
当一个对象拥有一个对象的锁标记的时候,在另一个对象的锁池等待的时候不会释放拥有的锁(一个男孩在拥有了一个女孩的锁标记的时候,还在等待另一个女孩的锁标记,在等待的时候不会释放上一个女孩的锁标记。).
当一个拥有一个对象的锁标记的线程访问这个对象的时候,其他的对象不能访问这个对象的同步方法,只能访问这个对象的非并发方法.(当一个女孩发出锁标记的时候,就是有了男朋友,这个对象就被加了锁,她定义的同步方法(拥抱,接吻,#¥%&%$#)只能这个男朋友访问,其他男孩不能访问,其他的男孩只能访问这个女孩的非加锁方法)呵呵.
当一个线程阻塞在一个对象的锁池里的时候,不会释放其所拥有的其他对象的的锁标记
每一个线程不释放自己拥有的资源,却申请别的线程,会造成死锁。
-------------------------------------------------------------------------------
(3) 解决死锁问题 进程间通信
(4) 每个对象有一个等待队列的空间.
(5) 等待机制:
调用wait()方法的条件,必需在这个同步代码块中.
当调用wait()方法的时候
线程t1: o.wait();
前提: 必需在对o加锁的同步代码块里.
结果:
1: t1会释放器所拥有的所有的锁标记.
2: t1会进入o的等待队列.
(6) 通知机制
线程t2: o.notify()/o.notifyAll(); [线程执行这个代码,全当是发善心,因为他可以把其他线程从井里面救出来,也可以不救出来]
前提: 必需在对o加锁的同步代码块里.
结果: 1: t2会从o的等待队列中释放一个线程/所有线程.
wait() 和 sleep()的联系和区别:
1. wait()是从Object继承下来的方法,而sleep()是Thread中的静态方法
2. wait() 和 sleep()都要阻塞运行,释放CPU资源
3. wait()要释放锁; 而sleep()不释放锁
wait()方法被调用时会解除锁定,但是我们能使用它的地方只是在一个同步方法或代码块内。
锁池: 是被迫进入,
等待队列: 是一个(井),在进去的时候把所有的资源释放,怎么才能出去,别人把你救出去.别人调用notify()/notifyAll()
进程之间进行通信需要一个标记值来调度两个线程.
例子: (生产者和消费者的例子)
public class TestPC {
public static void main(String[] args) {
MyStack stack=new MyStack();
PushThread t1=new PushThread(stack);
PushThread2 t3=new PushThread2(stack);
PopThread t2=new PopThread(stack);
t1.start();//其中一个生产者.
t3.start();//另一个生产者.
t2.start();//消费者.
}
}
class MyStack{//临界资源,要加锁。
char[] data=new char[6];
int index;//需要全局属性来调度.
public synchronized void push(char c){
if(data.length==index){
try {
this.wait();//释放自己所有的资源,包括自己拥有的锁.这样其他的线程就可以访问这个锁定的方法.
} catch (InterruptedException e) {
}
}
System.out.println(c+" pushed!!");
data[index]=c;
index++;//标记值加1.调度两个线程.
this.notifyAll();//从等待队列中把等待操作这个对象的线程调出来,把它从等待状态变成锁池状态.
this.print();//打印栈中的元素。
}
public synchronized void pop(){
if(index==0){
try {
this.wait();//释放自己所有的资源,包括自己拥有的锁.这样其他的线程就可以访问这个锁定的方法.
} catch (InterruptedException e) {
}
}
index--;//标记值减1;
System.out.println(data[index]+" poped!!");
data[index]=' ';
this.notifyAll();
this.print();
}
public void print(){
for(int i=0;i<index;i++){
System.out.print(data[i]+" ");
}
System.out.println();
}
}
class PushThread extends Thread{//操作这个栈的一个线程(入栈)。
MyStack stack;
public PushThread(MyStack stack) {
this.stack = stack;
}
public void run(){
for(char i='A';i<'Z';i++){
stack.push(i);//调用入栈方法.
System.out.println("PushThread1 ");
}
}
}
class PopThread extends Thread{//操作这个栈的另一个线程(出栈)。
MyStack stack;
public PopThread(MyStack stack) {
this.stack = stack;
}
public void run(){
for(char i='A';i<'Z';i++){
stack.pop();//调用出栈方法.
}
}
}
class PushThread2 extends Thread{
MyStack stack;
public PushThread2(MyStack stack) {
this.stack = stack;
}
public void run(){
for(char i='A';i<'Z';i++){
stack.push(i);
System.out.println("PushThread2");
}
}
}
----------------------------------------------------------------------------
作业:
新思路:必要时可利用Object中的 锁标记、锁池、等待队列。 用其wait() / notify() 方法,
自己制造临界资源来进行线程间通讯。
(参考字母数字交叉打印的例子TestNumberCharPrint.java)
其中注意o.notifyAll() 和 o.wait() 的调用顺序,还要注意边界问题,体现在最后要来一个判断if(c!=’Z’) o.wait(); 进而让程序正常结束。
public class HomeWork {
public static void main(String[] args) {
Run r = new Run();
Thread1 t1 = new Thread1(r);
Thread2 t2 = new Thread2(r);
t1.start();
t2.start();
}
}
class Run {
int index = 1;
public synchronized void printNum() {
for (int i = 1; i <= 52; i++) {
while (index % 3 == 0) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out.print(i + " ");
index++;
this.notifyAll();
}
}
public synchronized void printChar() {
for (char j = 'A'; j < 'Z'; j++) {
while (index % 3 != 0) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
index++;
System.out.print(j + " ");
this.notifyAll();
}
}
}
class Thread1 extends Thread {
Run r;
public Thread1(Run r) {
this.r = r;
}
public void run() {
r.printNum();
}
}
class Thread2 extends Thread {
Run r;
public Thread2(Run r) {
this.r = r;
}
public void run() {
r.printChar();
}
}
(1) 线程的同步
进程是一套顺序执行的指令。
同时开辟并行执行序列即多线程 run() 结束,线程就结束
JVM就是一个进程,在JVM中分出线程
进程数据空间独立;线程数据空间共享, 线程间通信更容易
分配的方式不如线程好,能充分使用CPU的资源。 共享数据就要加锁、解锁,会降低效率。
OS有 时分共享操作系统 和 实时操作系统
时分共享操作系统 时间片 调度系统把每个时间片分给多个进程 性能调优是根据时间片划分, 时间片会影响整个操作系统。
为了让某些工作并行,在进程主线程中开辟多个线程,这就是线程的机制。
我们关心的是被线程共享的数据,主要看synchronized加在何处。往往不加在线程里面,而是在共享的对象上。
只有运行状态的线程才有机会执行代码!
只有等到所有线程终止,进程才结束!
当一个对象分配给某线程锁标记时,其它线程不能访问同步方法,但能访问非同步方法!
起线程的两种方式:1、继承Thread,覆盖run()方法,用start()启动
2、实现Runnable接口,用来构造线程对象。(线程对象不是线程,但代表一个线程)
当多线程并发访问同一个对象(临界资源)的时候,如果原子操作被打破,则会造成数据的不一致.
在java中,用给临界资源加锁的机制来解决这个问题.
每一个对象都有一个互斥锁标记(monitor),只能分配给一个线程(对象相当于女孩,每个女孩都有一个锁标记,并且这个锁标记只能给一个男孩(线程)).
synchronized(o[对象的引用]){对O对象加锁的同步代码块}
synchronized可以作为一个方法的修饰符,对当前对象加锁.
例子:
public synchronized void m(){}
public void m(){
synchronized(this){}//对当前对象的方法加锁.
}
每一个对象都有一个锁池,它里面装的是等待该对象锁标记的线程.(就象每个女孩都有一个锁池,里面放着等待要这个女孩锁标记的男孩(线程))
一个线程可以拥有多个对象的标记.同步代码块是可以嵌套的(一个男孩可以有多个女孩的锁标记,但是一个女孩只能有一个男朋友)换句话说就是(一个线程可以拿到多个对象的锁标记,但是一个对象的锁标记只能给一个线程。).
synchronized(o1){
synchronized(o2){ //一个线程可以拥有多个对象的锁标记.
t1;
}
}
当一个对象拥有一个对象的锁标记的时候,在另一个对象的锁池等待的时候不会释放拥有的锁(一个男孩在拥有了一个女孩的锁标记的时候,还在等待另一个女孩的锁标记,在等待的时候不会释放上一个女孩的锁标记。).
当一个拥有一个对象的锁标记的线程访问这个对象的时候,其他的对象不能访问这个对象的同步方法,只能访问这个对象的非并发方法.(当一个女孩发出锁标记的时候,就是有了男朋友,这个对象就被加了锁,她定义的同步方法(拥抱,接吻,#¥%&%$#)只能这个男朋友访问,其他男孩不能访问,其他的男孩只能访问这个女孩的非加锁方法)呵呵.
当一个线程阻塞在一个对象的锁池里的时候,不会释放其所拥有的其他对象的的锁标记
每一个线程不释放自己拥有的资源,却申请别的线程,会造成死锁。
-------------------------------------------------------------------------------
(3) 解决死锁问题 进程间通信
(4) 每个对象有一个等待队列的空间.
(5) 等待机制:
调用wait()方法的条件,必需在这个同步代码块中.
当调用wait()方法的时候
线程t1: o.wait();
前提: 必需在对o加锁的同步代码块里.
结果:
1: t1会释放器所拥有的所有的锁标记.
2: t1会进入o的等待队列.
(6) 通知机制
线程t2: o.notify()/o.notifyAll(); [线程执行这个代码,全当是发善心,因为他可以把其他线程从井里面救出来,也可以不救出来]
前提: 必需在对o加锁的同步代码块里.
结果: 1: t2会从o的等待队列中释放一个线程/所有线程.
wait() 和 sleep()的联系和区别:
1. wait()是从Object继承下来的方法,而sleep()是Thread中的静态方法
2. wait() 和 sleep()都要阻塞运行,释放CPU资源
3. wait()要释放锁; 而sleep()不释放锁
wait()方法被调用时会解除锁定,但是我们能使用它的地方只是在一个同步方法或代码块内。
锁池: 是被迫进入,
等待队列: 是一个(井),在进去的时候把所有的资源释放,怎么才能出去,别人把你救出去.别人调用notify()/notifyAll()
进程之间进行通信需要一个标记值来调度两个线程.
例子: (生产者和消费者的例子)
public class TestPC {
public static void main(String[] args) {
MyStack stack=new MyStack();
PushThread t1=new PushThread(stack);
PushThread2 t3=new PushThread2(stack);
PopThread t2=new PopThread(stack);
t1.start();//其中一个生产者.
t3.start();//另一个生产者.
t2.start();//消费者.
}
}
class MyStack{//临界资源,要加锁。
char[] data=new char[6];
int index;//需要全局属性来调度.
public synchronized void push(char c){
if(data.length==index){
try {
this.wait();//释放自己所有的资源,包括自己拥有的锁.这样其他的线程就可以访问这个锁定的方法.
} catch (InterruptedException e) {
}
}
System.out.println(c+" pushed!!");
data[index]=c;
index++;//标记值加1.调度两个线程.
this.notifyAll();//从等待队列中把等待操作这个对象的线程调出来,把它从等待状态变成锁池状态.
this.print();//打印栈中的元素。
}
public synchronized void pop(){
if(index==0){
try {
this.wait();//释放自己所有的资源,包括自己拥有的锁.这样其他的线程就可以访问这个锁定的方法.
} catch (InterruptedException e) {
}
}
index--;//标记值减1;
System.out.println(data[index]+" poped!!");
data[index]=' ';
this.notifyAll();
this.print();
}
public void print(){
for(int i=0;i<index;i++){
System.out.print(data[i]+" ");
}
System.out.println();
}
}
class PushThread extends Thread{//操作这个栈的一个线程(入栈)。
MyStack stack;
public PushThread(MyStack stack) {
this.stack = stack;
}
public void run(){
for(char i='A';i<'Z';i++){
stack.push(i);//调用入栈方法.
System.out.println("PushThread1 ");
}
}
}
class PopThread extends Thread{//操作这个栈的另一个线程(出栈)。
MyStack stack;
public PopThread(MyStack stack) {
this.stack = stack;
}
public void run(){
for(char i='A';i<'Z';i++){
stack.pop();//调用出栈方法.
}
}
}
class PushThread2 extends Thread{
MyStack stack;
public PushThread2(MyStack stack) {
this.stack = stack;
}
public void run(){
for(char i='A';i<'Z';i++){
stack.push(i);
System.out.println("PushThread2");
}
}
}
----------------------------------------------------------------------------
作业:
新思路:必要时可利用Object中的 锁标记、锁池、等待队列。 用其wait() / notify() 方法,
自己制造临界资源来进行线程间通讯。
(参考字母数字交叉打印的例子TestNumberCharPrint.java)
其中注意o.notifyAll() 和 o.wait() 的调用顺序,还要注意边界问题,体现在最后要来一个判断if(c!=’Z’) o.wait(); 进而让程序正常结束。
public class HomeWork {
public static void main(String[] args) {
Run r = new Run();
Thread1 t1 = new Thread1(r);
Thread2 t2 = new Thread2(r);
t1.start();
t2.start();
}
}
class Run {
int index = 1;
public synchronized void printNum() {
for (int i = 1; i <= 52; i++) {
while (index % 3 == 0) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out.print(i + " ");
index++;
this.notifyAll();
}
}
public synchronized void printChar() {
for (char j = 'A'; j < 'Z'; j++) {
while (index % 3 != 0) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
index++;
System.out.print(j + " ");
this.notifyAll();
}
}
}
class Thread1 extends Thread {
Run r;
public Thread1(Run r) {
this.r = r;
}
public void run() {
r.printNum();
}
}
class Thread2 extends Thread {
Run r;
public Thread2(Run r) {
this.r = r;
}
public void run() {
r.printChar();
}
}