摘要小节:
1.同步机制
2.wait和notify
3.守护进程
4.stack栈
5.Timer类和//Runtime类的简单讲解
(一)同步synchronized机制
为防止多线程的时候因为争抢资源而同时访问某个变量导致数据的不一致,需要引入java 的同步机制
java用关键字synchronized(同步)实现
无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁——而且同步方法很可能还会被其他线程的对象访问。
每个对象只有一个锁(lock) 与之相关联。实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
当我们明确知道要对某个对象加锁,而非整个类的实例加锁时,应该使用synchronized (ob ject),注意不能对基本数据类型直接加Synchronized关键字。同时当我们的成员变量需要加锁时,应该将该变量设置为private,并且提供Synchronized修改的get方法,以保证该成员遵循同步机制。
实例:银行存钱取钱
(同步函数)
package bNineFirst;
public class Bank { //个人账户
private double money = 0; //账户余额
public synchronized void addMoney(double count){ //存钱
money += count;
System.out.println("存入"+count+"元,当前余额为"+money+"元");
}
public synchronized void subMoney(double count){ //取钱
if(count > money){
System.out.println("对不起,余额不足");
}else{
money -= count;
System.out.println("取出"+count+"元,当前余额为"+money+"元");
}
}
public void lookMoney(){ //查看余额
System.out.println("当前余额为"+money+"元");
}
}
package bNineFirst;
public class AddAndSubMoney {
Bank bank = new Bank();
class Add extends Thread{
public void run(){
while(true){
bank.addMoney(100);
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Sub extends Thread{
public void run(){
while(true){
bank.subMoney(100);
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void execute(){
Add a = new Add();
Sub s = new Sub();
a.start();
s.start();
}
public static void main(String [] args){
AddAndSubMoney asm = new AddAndSubMoney();
asm.execute();
}
}
(同步代码块)
package bNineFirst;
public class Bank { //个人账户
private double money = 0; //账户余额
public void addMoney(double count){ //存钱
synchronized(this){ //对象本身
money += count; //只需要这块代码实现同步即可
}
System.out.println("存入"+count+"元,当前余额为"+money+"元");
}
public void subMoney(double count){ //取钱
synchronized(this){
if(count > money){
System.out.println("对不起,余额不足");
}else{
money -= count;
System.out.println("取出"+count+"元,当前余额为"+money+"元");
}
}
}
public void lookMoney(){ //查看余额
System.out.println("当前余额为"+money+"元");
}
}
其余的之前的一样:
同步代码块和同步函数实现的效果是一样的,区别在于,同步代码块只是设置需要同步的那块代码,同步是需要消耗系统性能的,同步函数会对函数内的所有内容同步,所以,如果希望代码既实现线程安全又希望性能较好,应该使用同步代码块。
像是数据结构中的vector实现了同步机制,是线性安全的,他的每个函数都加了synchronized,其他操作和arraylist一样,但速度不比arraylist
在平常,因为我的程序都是单线程的,所以使用arraylist较多(线程不安全,性能好),但是工作中一般使用vector
(二)wait和notify
wait ()必须在synchronized方法或代码块内部使用,wait() 会让已经获得synchronized方法或代码块控制权的Thread暂时休息,并且丧失控制权。此时,由于该线程丧失控制权并且进入等待状态,所以其他线程就能取得控制权,并且在适当情况下调用notifyAll()来唤醒wait()的线程。
需要注意的是,被唤醒的线程由于已经丧失了控制权,所以需要等待唤醒它的线程结束操作,从而才能重新获得控制权。notifyAll ()并不是让当前线程马上让出控制权,而只是让其他wait ()当中的线程唤醒而已。
和sleep()区别:
sleep()设置了时间,时间过后,会自动唤醒
wait()如果不用方法去唤醒,他就永远在等待
调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
为何这wait()、notify()、notifyAll()不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。
实例:生产与消费
package bNineFirst;
public class WorkShop {
private int count = 0; //产品数量
public synchronized void Produce(){
if(count < 20){ //防止产品过剩
count++;
System.out.println("开始生产第"+count+"件产品");
notify(); //唤醒消费线程
}else{ //如果产品生产过多,就停一下
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void Buy(){
if(count > 0){ //确保有产品的情况下才能消费
System.out.println("开始消费第"+count+"件产品");
count--;
notify(); //唤醒生产线程
}else{ //如果没有产品了,就停一下,等待生产
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package bNineFirst;
public class ProduceAndBuyNotify {
WorkShop ws = new WorkShop();
class Pro extends Thread {
public void run() {
while (true) {
ws.Produce();
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Buy extends Thread {
public void run() {
while (true) {
ws.Buy();
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void exetute() {
Pro p = new Pro();
Buy b = new Buy();
p.start();
b.start();
}
public static void main(String[] args) {
ProduceAndBuyNotify pb = new ProduceAndBuyNotify();
pb.exetute();
}
}
(三)守护进程
java中提供了两种线程,用户线程和守护线程
用户线程是高优先级的线程。JVM虚拟机在结束一个用户线程之前,会先等待该用户线程完成它的task。
守护线程是低优先级的线程,它的作用仅仅是为用户线程提供服务。正是由于守护线程是为用户线程提供服务的,仅仅在用户线程处于运行状态时才需要守护线程。另外,一旦所有的用户线程都运行完毕,那么守护线程是无法阻止JVM退出的。这也是存在于守护线程中的无限循环不会产生问题的原因,因为包括finally 块的任何代码都不会被执行,一旦所有的用户线程结束运行之后。
当程序中所有的其他线程都结束时,即便守护线程中还有没有运行的代码(垃圾回收机制,只要JVM启动,始终在运行),也会直接结束。JVM退出执行。
package bNineFirst;
public class ShouHuJingCheng {
class One extends Thread{
public void run(){
System.out.println(Thread.currentThread().getName()+" 开始");
try {
sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 结束");
}
}
public void execute(){
One one = new One();
one.setName("one");
one.setDaemon(true); //在进程启动之前将进程设置为守护进程
one.start();
}
public static void main(String [] args){
System.out.println("主线程开始");
ShouHuJingCheng sh = new ShouHuJingCheng();
sh.execute();
System.out.println("主线程结束");
}
}
用户进程,没有将进程设置为守护进程时的输出请况
守护进程
(主线程结束了,所以one线程不会等三秒让他执行完,就直接结束了)
Main线程结束,其他线程一样可以正常运行。
主线程,只是个普通的非守护线程,用来启动应用程序,不能设置成守护线程;除此之外,它跟其他非守护线程没有什么不同。主线程执行结束,其他线程一样可以正常执行。
按照操作系统的理论,进程是资源分配的基本单位,线程是CPU调度的基本单位。对于CPU来说,实并不在java的主线程和子线程之分,都只是个普通的线程。进程的资源是线程共享的,只要进程还在,线程就可以正常执行,换句话说线程是强依赖于进程的。也就是说,线程其实并不存在互相依赖的关系,一个线程的死亡从理论上来说,不会对其他线程有什么影响。
(四)stack栈
是用vector实现的,所以也是线程安全的
主要的操作有:
push(Object obj):压栈
Object obj = pop():出栈
Object obj = peek():查看栈顶元素
boolean bol = empty():判断栈是否为空
int num = search(Object obj):返回元素在栈中的位置,栈顶为1,栈顶为栈的长度
实例:
package bNineFirst;
import java.util.Stack;
public class StackTest {
public static void main(String [] args){
Stack<String > s = new Stack<String>();
s.push("a");
System.out.println("one:"+s.size());
s.push("b");
System.out.println("two:"+s.size());
String str1 = s.pop();
System.out.println("three:"+str1 +s.size());
String str2 = s.peek();
System.out.println("four:"+str1+str2 +s.size());
boolean bol = s.empty();
System.out.println("five:"+bol +s.size());
Stack s1 = new Stack();
boolean bol2= s1.empty();
System.out.println("six:"+bol2 +s1.size());
s.push("x");
s.push("y");
int num = s.search("a");
System.out.println("seven:"+ num +s.size());
}
}
执行效果:
(五)Timer类和Runtime类
Timer
简单的调度类
按照一定的时间规律来循环执行代码
package bNineFirst;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String [] args){
Timer timer = new Timer();
// void java.util.Timer.schedule(TimerTask task, long delay, long period)
// 任务 延迟多少时间开始 每次的时间间隔
timer.schedule(new TimerTask(){ //任务匿名类
public void run(){ //线程的run方法
System.out.println("懒猪,起床啦!");
}
}, 1000, 2000);
}
}
执行效果:
(执行后,隔1s中开始执行,接下来每隔2s开始打印)
Runtime类
代表Java程序的运行环境,是一个单例模式类。
package bNineFirst;
import java.io.IOException;
public class RuntimeTest {
public static void main(String [] args){
Runtime r = Runtime.getRuntime(); //单例模式,创建类的对象
try {
r.exec("D:\\studySoftware\\myvmware\\vmware.exe");//打开vmware
} catch (IOException e) {
e.printStackTrace();
}
}
}
java程序中为了防止将‘\’变成转义符,所以需要两个'\',或者是'/'
(1)D:\\studySoftware\\myvmware\\vmware.exe
(2)D:/studySoftware/myvmware/vmware.exe
【只有里面方法是同步的,我们才能说它是线程安全的】
【当阻塞方法收到中断请求的时候就会抛出InterruptedException异常(所有该异常都和多线程有关系)】