时间:2016-4-15 09:56
——多线程(还有多核编程)
1、进程
进程是一个正在执行中的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
多任务的理解:
计算机可以运行多个任务,哪个任务运行,哪个任务就占用CPU资源。
2、线程(例:Thunder)
线程就是进程中的一个独立的控制单元。
线程是对进程的进一步分化。
线程控制着进程的执行。
一个进程中至少有一个线程。
举例:
JVM启动的时候会有一个java.exe进程,该进程中至少有一个线程负责Java程序的执行,而这个线程运行的代码存在于main方
法中,该线程称为主线程。其实JVM启动时不止一个线程,还有负责垃圾回收机制的线程。
3、多线程存在的意义
提高效率
4、线程的创建方式
通过对Java API的查找,Java已经提供了对线程这类事物的描述,就是Thread类。
创建线程的第一种方式:继承Thread类。
步骤:
1、定义类继承Thread。
2、复写Thread类中的run方法。
目的是将自定义的代码存储在run()方法中,让线程执行指定的代码。
3、调用线程的start方法。
该方法有两个作用:启动线程,调用run方法。只有start才能调用线程。
如果直接调用run方法,则为主线程调用。线程创建了,并没有执行。
run仅仅是封装线程代码。
为什么要覆盖run方法?
Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,
也就是说Thread类中的run方法,用于存储线程要运行的代码,而主线程运行的代码存放在main方法中。
当主线程结束后,只要还有线程在运行,那么进程就不停止。
在下面的例子中main为主线程。
class thread extends Thread
{
public void run()
{
for(int i = 0; i<20; i++)
System.out.println("thread--"+i);
}
}
public class ThreadDemo1 {
public static void main(String[] args)
{
thread t = new thread();
t.start();
//t.run();
for(int i = 0; i<20; i++)
{
System.out.println("main--"+i);
}
}
}
——————————输出结果为
main--0
thread--0
main--1
thread--1
main--2
thread--2
main--3
thread--3
main--4
thread--4
main--5
main--6
thread--5
main--7
thread--6
main--8
thread--7
main--9
thread--8
thread--9
main--10
main--11
main--12
main--13
thread--10
thread--11
thread--12
thread--13
main--14
thread--14
main--15
thread--15
main--16
thread--16
main--17
thread--17
main--18
thread--18
main--19
thread--19
程序运行完发现每次运行结果都不同,因为多个线程都在获取CPU的执行权,CPU执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个程序在运行(多核CPU除外)。
CPU在做着快速的切换,以达到看上去是同时运行的效果,我们可以形象把多线程的运行形容为在互相抢夺CPU的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,CPU说了算。
5、多线程的特性
随机性。
6、多线程的运行状态
被创建:被创建start()后进入运行状态。
当同一个线程被start()两次之后,第二次会出现java.lang.IllegalThreadStateException线程状态异常,已经开启的线程
是不允许再次开启的。
运行:运行过程中通过sleep(time)和wait()进入冻结状态,sleep(time)时间到之后自动唤醒,而wait()需要notify()来手动唤醒。
冻结:放弃了执行资格。
阻塞(临时状态):具备运行资格,但没有执行权,CPU未切换到该线程。
消亡:stop()和run()方法结束。
sleep(time)
wait() --- 需要notify()唤醒。
stop()
7、获取线程对象以及名称
可以通过set和get方法设置和获取名称。
Thread(String name)
Thread初始化的时候就可以设置名称。
线程都有自己默认的名称:thread-编号,编号从0开始。
static Thread currentThread()
返回当前正在执行的线程对象的引用(线程对象)。
因为currentThread是静态的,所以没有访问当前线程对象的特有数据。
获取线程名称:
getName();
设置线程名称
setName()或者构造函数
System.out.println(Thread.currentThread().getName());
Thread类中有getName()和setName()方法,可以直接使用super(name)传参设置线程名称,
因为父类Thread中有Thread(String name)。
8、创建线程的第二种方式:实现Runnable接口
class Run{}
new Thread(new Run()).start();
继承Runnable接口的子类并不是线程,因为该子类和Thread类无关。
Thread类有一个构造方法,可以接收一个Runnable接口类型的对象。
如果有一部分代码需要多个线程去执行,就需要这个代码所在的类继承Thread,但是如果该类已经继承了其他类,就不能继
承Thread类,只能使用Runnable接口来实现。
总结:
可以在继承一个类的同时实现Runnable接口。
步骤:
(1)定义类实现Runnable接口。
(2)覆盖Runnable接口中的run方法。
将线程要运行的代码存放在该run方法中。
(3)通过Thread类建立线程对象。(只有Thread能创建线程对象)
(4)将Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
通过多个线程操作一个对象来保证数据的完整性和唯一性。
Thread(Runnable target)
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为:
自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所
属对象。
(5)调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
实现方式和继承方式有什么区别?
实现方式的好处:避免了单继承的局限性。
在定义线程时,建议使用实现方式。
如果只有继承,那么Student类中有一部分代码需要多线程去执行时,继承Thread类之后无法再继承Person类,为了避免此情况的
发生,就出现了接口。
因为Java工程师并不确定将来会出现的对象类型,则Thread定义构造方法的接收的是Runnable类型的引用,这样不论是什么类型
的对象,都能接收。
两种方式的区别:
继承Thread类:线程代码 存放在Thread子类的run方法中。a
实现Runnable接口:线程代码存放在Runnable接口的子类对象的run方法中。
——通过多线程实现多窗口售票例子:
class Ticket implements Runnable
{
//当将i定义为静态时,所有线程共享一个数据,保证数据唯一性。
private int tick = 50;
public void run()
{
while (tick > 0)
{
System.out.println(Thread.currentThread().getName()+"----"+tick--);
}
}
}
public class TicketDemo {
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//此时通过Thread类创建四个对象共同开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
------------------------------------------------------------------------
输出结果:
Thread-0----50
Thread-2----48
Thread-1----49
Thread-2----46
Thread-0----47
Thread-2----44
Thread-2----42
Thread-1----45
Thread-2----41
Thread-2----39
Thread-2----38
Thread-2----37
Thread-2----36
Thread-2----35
Thread-2----34
Thread-2----33
Thread-0----43
Thread-2----32
Thread-1----40
Thread-2----30
Thread-2----28
Thread-0----31
Thread-2----27
Thread-2----25
Thread-2----24
Thread-2----23
Thread-1----29
Thread-2----22
Thread-2----20
Thread-0----26
Thread-2----19
Thread-2----17
Thread-2----16
Thread-2----15
Thread-2----14
Thread-1----21
Thread-2----13
Thread-0----18
Thread-2----11
Thread-1----12
Thread-2----9
Thread-0----10
Thread-2----7
Thread-2----5
Thread-2----4
Thread-2----3
Thread-2----2
Thread-2----1
Thread-1----8
Thread-0----6
------------------------------------------------------------------------
通过上面售票的例子发现,虽然数据保证完整性了但是输出顺序与理想不一致,是因为线程间的切换导致的。
9、多线程的安全问题——同步代码块 synchronized
问题出现的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,此时另一个线程参与执行,导致
共享数据错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
class Ticket implements Runnable
{
private int tick = 100;
Object obj = new Object();
public void run()
{
while (true)
{
synchronized(obj)
{
if(tick > 0)
{
try
{
Thread.sleep(10); // sleep()方法需要抛出InterruptedException异常,但是如果复写了Runnable的run方法,则只
能try,不能抛出。
}
catch(Exception ex)
{
}
System.out.println(Thread.currentThread().getName()+"----"+tick--);
}
}
}
}
}
public class TicketDemo {
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
锁旗标:
对象如同锁,持有锁的线程可以在程序中同步执行,未持有锁的线程即使获取CPU的执行权也无法执行,因为没有锁。
同步的前提:
1、必须要有两个或者两个以上的线程才需要同步。
2、必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程运行。
synchronized的好处:
解决了多线程的安全问题。
弊端:
多个线程都需要判断锁,较为消耗资源。
当多线程出现安全隐患,如何查找问题所在(明确哪些代码需要被同步):
1、明确哪些代码是多线程运行代码。
2、明确共享数据。
3、明确多线程运行代码中哪些语句是操作共享数据的。
同步函数:
run方法不能加synchronized锁,如果加锁就会变成单线程程序,因为第一个线程开启之后run方法就会上锁,则其余线程就无法再
次进入,直到第一个线程全部运行结束。
public synchronized void show(){ }
同步函数使用的锁是this锁。
因为函数需要被对象调用,那么函数都有一个所属对象的引用,就是this。
使用同步锁封装代码时可以将代码提取使用函数封装并同步,就有了同步函数。
当同步代码块和同步函数分离时,如果当前对象是自定义对象锁而不是this锁,则会容易出现数据混乱的情况,因为同步的前提是
多个线程使用同一个锁。
/**
*
* 验证同步函数使用的锁是this锁
* @author WYC
*
*/
public class ThreadDemo04 {
public static void main(String[] args) throws InterruptedException
{
Ticket2 t = new Ticket2();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
Thread.sleep(1);
t.flag = false;
t2.start();
}
}
class Ticket2 implements Runnable
{
private static int tick = 1000;
Object o = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(Ticket2.class)
{
if(tick > 0)
{
try{Thread.sleep(1);}catch(Exception ex){}
System.out.println(Thread.currentThread().getName() + "--" + tick--);
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public static synchronized void show()
{
if(tick > 0)
{
try{Thread.sleep(10);}catch(Exception ex){}
System.out.println(Thread.currentThread().getName() + "----" + tick--);
}
}
}
11、静态方法的同步
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证发现不再是this,因为静态方法中不可以定义this。静态进内存时内存中没有本类对象,但是一定有该类对应的字节码
文件,类名.class,该对象的类型是Class。
静态方法的同步,使用的锁是该方法所在类的字节码文件对象。
类名.class
同步静态方法的锁是加在类上,同步非静态方法的锁是加在那个对象上。
例:
synchronized(类.class){ }
public static synchronized void show(){ }
以上两个代码会保证同步,因为使用的锁是同一个锁,如果synchronized使用的锁是对象锁,则会出现数据混乱的情况。
静态方法属于类,普通方法属于对象,再一起加上同步,即同步静态方法就是给类加锁,同步普通方法就是给对象加锁。
12、多线程中的单例设计模式
懒汉式
class Single
{
private Single(){};
private Single s = null;
public static Single getInstance()
{
if(null == s) //在锁之外再加一个if判断,可以提高效率,避免每次都判断锁。
{
synchronized(Single.clsss)
//为了保证对象唯一性,防止多线程并发访问该方法导致出现多个对象,所以使用synchronized同步该方法
{
if(null == s)
s = new Single();
}
}
return s;
}
}
13、死锁 DeadLock
package duoxiancheng;
class Lock implements Runnable
{
private boolean flag;
Lock(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
synchronized(LockInstance.locka)
{
System.out.println("if locka");
synchronized(LockInstance.lockb)
{
System.out.println("if lockb");
}
}
}
else
{
synchronized(LockInstance.lockb)
{
System.out.println("else locka");
synchronized(LockInstance.locka)
{
System.out.println("else lockb");
}
}
}
}
}
class LockInstance
{
//LockInstance lock1 = new LockInstance();
//LockInstacne lock2 = new LockInstance();
static Object locka = new Object();
static Object lockb = new Object();
//Object lock1 = new Object();
}
public class DeadLock {
public static void main(String[] args)
{
Lock l1 = new Lock(true);
Lock l2 = new Lock(false);
Thread t1 = new Thread(l1);
Thread t2 = new Thread(l2);
t1.start();
t2.start();
}
}
14、线程间的通讯
其实就是多个线程在操作同一个资源,但是操作的动作不同(比如存取)。
class Student
{
String name;
String sex;
}
class Input implements Runnable
{
private Student st;
Input(Student st)
{
this.st = st;
}
public void run()
{
int x = 30;
boolean bool = true;
while(true)
{
synchronized(st) //为了避免出现同步问题,在输入和输出语句外加上synchronized代码块,对象选择内存中唯一的st对象
{
if(bool)
{
st.name = "张三";
st.sex = "男";
bool = false;
}
else
{
st.name = "zhangsan";
st.sex = "nan";
bool = true;
}
}
}
}
}
class Output implements Runnable
{
private Student st;
Output(Student st)
{
this.st = st;
}
public void run()
{
int x = 30;
while(true)
{
synchronized(st)
{
System.out.println(st.name+"-------"+st.sex);
System.out.println(Thread.currentThread().getName());
}
}
}
}
public class Threadtongxun {
public static void main(String[] args)
{
Student st = new Student();
Input in = new Input(st);
Output out = new Output(st);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
15、等待——唤醒机制
线程运行的时候内存中会建立一个线程池,等待的线程都存在于线程池中。
notify();唤醒的通常是第一个进入线程池的线程。notifyAll();唤醒所有线程池中的线程。
wait(); notify(); notifyAll();必须用于同步中,因为只有同步才具有锁。
wait()用于操作线程,但是却定义在了Object类里面,这是为什么?
因为wait() notify方法在操作同步中的线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的等待线程可以被同一
个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
使用wait()时必须标识出wait()操作的线程所属的锁。
例如:
obj.wait() obj.notify()
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以,可以被任意对象调用的方法就定义在Object类中。
class Student
{
String name;
String sex;
boolean flag = false;
}
class Input implements Runnable
{
private Student st;
Input(Student st)
{
this.st = st;
}
public void run()
{
int x = 30;
boolean bool = true;
while(true)
{
synchronized(st)
{
if(st.flag)
{try{st.wait();}catch(Exception ex){}}
if(bool)
{
st.name = "张三";
st.sex = "男";
bool = false;
}
else
{
st.name = "zhangsan";
st.sex = "nan";
bool = true;
}
st.flag = true;
st.notify();
}
}
}
}
class Output implements Runnable
{
private Student st;
Output(Student st)
{
this.st = st;
}
public void run()
{
int x = 30;
while(true)
{
synchronized(st)
{
if(!st.flag)
{try{st.wait();}catch(Exception ex){}}
System.out.println(st.name+"-------"+st.sex);
System.out.println(Thread.currentThread().getName());
st.flag = false;
st.notify();
}
}
}
}
public class Threadtongxun {
public static void main(String[] args)
{
Student st = new Student();
Input in = new Input(st);
Output out = new Output(st);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
16、Consumer和Producer
class Person
{
private String name;
private int count = 100;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag) //此处如果使用if,则会导致其他线程不会判断flag(因为有两个以上线程),所以使用while多次判断
try
{
this.wait();
}
catch(Exception ex)
{
}
this.name = name+"--"+count--;
System.out.println(Thread.currentThread().getName()+"Producer--"+this.name);
flag = true;
this.notifyAll(); //如果使用notify,则只会唤醒本方线程,会导致全部线程都进入等待状态,需要使用notify唤醒全部线程,
//然后再次进行判断。
}
public synchronized void out()
{
while(!flag)
{
try
{
this.wait();
}
catch(Exception ex)
{
}
System.out.println(Thread.currentThread().getName()+"Consumer-----"+this.name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable
{
private Person p;
Producer(Person p)
{
this.p = p;
}
public void run()
{
while(true)
{
p.set("++Goods++");
}
}
}
class Consumer implements Runnable
{
private Person p;
Consumer(Person p)
{
this.p = p;
}
public void run()
{
while(true)
{
p.out();
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args)
{
Person p = new Person();
Producer pro = new Producer(p);
Consumer con = new Consumer(p);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
17、JDK5.0升级版
JDK1.5中提供了多线程升级解决方案,将synchronized替换成显式的Lock操作,将Object中的wait、notify、notifyAll,替换成
了Condition对象,该对象可以通过Lock锁进行获取。
该示例中,实现了本方只唤醒对方的操作。
import java.util.concurrent.locks.*;
class Producer implements Runnable // throws InterruptedException
{
private Resource r;
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("++商品++");
}
}
}
class Consumer implements Runnable //throws InterruptedException
{
private Resource r;
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name) //throws InterruptedException
{
lock.lock();
try
{
while(flag)
condition_pro.await();
this.name = name+"..."+count--;
System.out.println(Thread.currentThread().getName()+"--生产者--"+this.name);
flag = true;
condition_con.signal();
}
catch(Exception ex)
{
}
finally
{
lock.unlock();
}
}
public void out() //throws InterruptedException
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
flag = false;
condition_pro.signal();
}
catch (Exception ex)
{
}
finally
{
lock.unlock();
}
}
}
public class ProducerConsumerDemo1_5 {
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
18、如何停止线程?
只有一种方法,就是run方法结束,开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是
线程结束。
特殊情况:
当线程处于冻结状态,就不会读取到标记,那么线程就不会结束,当没有指定的方式让冻结的线程恢复到运行状态时,这时需要
对冻结状态进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记(循环条件)让线程结束。
Thread类提供该方法:interrupt();
使用方法:对象.interrupt();
class StopThread implements Runnable
{
private boolean flag = true;
public synchronized void run()
{
while(flag)
{
try
{
wait();
//System.out.println(Thread.currentThread().getName()+"----running");
}
catch(InterruptedException ex)
{
flag = false;
System.out.println(Thread.currentThread().getName()+" InterrputException");
}
System.out.println(Thread.currentThread().getName()+" over");
}
}
}
public class StopThreadDemo {
public static void main(String[] args)
{
StopThread s = new StopThread();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
int x = 0;
while(true)
{
if(x++ == 60)
{
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread()+"--"+x);
}
}
}
19、守护线程
public final void setDaemon(boolean on) //如果参数为true,则该线程为守护线程
将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,Java虚拟机退出,该方法必须在启动线程前调用。
后台线程:当所有的前台线程都结束后,后台线程会自动结束。
class StopThread implements Runnable
{
private boolean flag = true;
public void run()
{
while(true)
{
if(flag)
try
{
wait();
}
catch(Exception ex)
{
}
System.out.println(Thread.currentThread().getName()+" run");
}
}
}
public class SetDaemonDemo {
public static void main(String[] args)
{
StopThread s = new StopThread();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int x = 0;
w:while(true)
{
if(x++ == 60)
{
//t1.interrupt();
//t2.interrupt();
break w;
}
System.out.println(Thread.currentThread()+"--"+x);
}
System.out.println("main over");
}
}
20、join方法
需要抛出throws InterruptedException
join方法的功能是等待该线程结束主线程再启动,例如t1.join();
如果调用顺序为:
t1.start();
t2.start();
t1.join();
则运行顺序为:
t1 t2交替运行,主线程等到t1结束再运行。
class Join implements Runnable
{
public void run()
{
for(int i = 60;i>0;i--)
{
System.out.println(Thread.currentThread().getName() + "---"+i);
}
}
}
public class JoinDemo {
public static void main(String[] args) throws InterruptedException
{
Join j = new Join();
Thread t1 = new Thread(j);
Thread t2 = new Thread(j);
t1.start();
t2.start();
t1.join();
int x = 50;
while(x>0)
{
System.out.println(Thread.currentThread().getName()+"--"+x--);
}
System.out.println("main over");
}
}
21、优先级与yield方法
线程类中有一个方法,toString(),覆盖了Object类中的toString,返回该线程的字符串表现形式,包括线程名称、优先级和线程组。
线程组:谁开启的线程,线程就属于哪个组。
优先级:所有的线程默认优先级是5,可以通过setPriority()来修改线程的优先级。setPriority方法属于ThreadGroup类。
Java中优先级1、5、10默认定义为MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY:t1.setPriority(MAX_PRIORITY),将t1
的优先级设置为10级。
数据是固定的定义成常量,字母大写,数据是共享的定义成静态。
yield()方法:暂停当前执行的线程,执行其他线程。
可以暂时释放当前线程的执行权,防止连续执行,导致其他线程无执行权。
22、内部类线程
public class NeibuleiThreadDemo {
public static void main(String[] args)
{
new Thread()
{
public void run()
{
int x = 30;
while(x-- > 0)
{
System.out.println(Thread.currentThread().toString()+"--"+x);
}
}
}.start();
}
}
——ThreadLocal
1、ThreadLocal类只有三个方法:
* void set(T value)
保存值。
* T get()
获取值。
* void remove()
移除值。
2、ThreadLocal的内部是一个Map
ThreadLocal内部其实是使用一个Map来保存数据的,虽然在使用ThreadLocal时只给出了值,并没有给出键,但是在其内部使用了当前线程作为键。
class TL<T> {
private Map<Thread, T> map = new HashMap<Thread, T>();
public void set(T value) {
// 使用当前线程做key
map.put(Thread.currentThread(), value);
}
public T get() {
return map.get(Thread.currentThread());
}
public void remove() {
map.remove(Thread.currentThread());
}
}
3、具体操作
public class Demo012 {
@Test
public void fun1() {
ThreadLocal<String> tl = new ThreadLocal<String>();
tl.set("Hello");// 存
tl.set("World");// 取
String s = tl.get();
tl.remove();// 删
System.out.println(s);
}
}
- 20145213《Java程序设计学习笔记》第六周学习总结
20145213<Java程序设计学习笔记>第六周学习总结 说在前面的话 上篇博客中娄老师指出我因为数据结构基础薄弱,才导致对第九章内容浅尝遏止地认知.在这里我还要自我批评一下,其实我事后 ...
- Java IO学习笔记五:BIO到NIO
作者:Grey 原文地址: Java IO学习笔记五:BIO到NIO 准备环境 准备一个CentOS7的Linux实例: 实例的IP: 192.168.205.138 我们这次实验的目的就是直观感受一 ...
- Java程序设计学习笔记(一)
时间:2015-6-2 23:04 程序员,程序猿,程序媛,码农 ------------------------------------------------------- --Java的应用 ...
- Java设计模式学习笔记(五) 单例模式
前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 使用单例模式的原因 以Windows任务管理器为例,在Windows系统中,任务管理器是唯 ...
- java基础学习笔记五(抽象类)
java基础学习总结——抽象类 抽象类介绍
- Java基础学习笔记五 Java基础语法之面向对象
面向对象 理解什么是面向过程.面向对象 面向过程与面向对象都是我们编程中,编写程序的一种思维方式.面向过程的程序设计方式,是遇到一件事时,思考“我该怎么做”,然后一步步实现的过程.例如:公司打扫卫生( ...
- Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)
多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...
- Java基础学习笔记(五) - 常用的API
API介绍 概念:API 即应用编程程序接口.Java API是JDK中提供给我们使用的类说明文档,这些类将底层的代码实现封装.无需关心这些类是如何实现,只需要学习如何使用. 使用:通过API找到需要 ...
- Java程序设计学习笔记(六) — 网络编程
时间:2016-5-8 02:03 --网络编程 网络传输实际上就是进行数据传输. 一.传输的步骤: 1.对对方IP地址进行定位. 2.将数据打包发送到 ...
随机推荐
- Hive——基本DDL语句
Hive--基本DDL语句 DDL:Data Definition Language(数据定义语言,与关系型数据库相似) 官方手册:https://cwiki.apache.org/confluenc ...
- deepin安装Motrix,cocomusic
1,motrix(下载工具):https://motrix.app/ 2,cocomusic(开源音乐播放器):https://github.com/xtuJSer/CoCoMusic/release ...
- 前端早读课:JavaScript函数的6个基本术语
lambdas(匿名函数) 箭头函数. const lambda = (a, b) => a + b; first-class functions(头等函数) 该类型可以用作变量的值. docu ...
- mybatis-7-缓存
1. 一级缓存: SqlSession 级别, 默认开启, 并且不能关闭 操作数据库是需要创建 SqlSession 对象, 在对象中有一个 HashMap 用于存储缓存数据, 不同的 SqlSess ...
- 完整的URL是怎样的?
完整的URL字段解读: URL:http://localhost:80/MzyPractice/chapter10/testb.php?name=Mei&radio=Test#dowel ht ...
- Vulnhub -- Jarbas靶机渗透
目标:拿到服务器的Shell 信息收集 配置好后用nmap扫描 kali's ip:192.168.241.131 nmap -sP 192.168.241.131/24 一个个扫描 发现192.16 ...
- C++第四十篇 -- 研究一下Windows驱动开发(三)-- NT式驱动的基本结构
对于NT式驱动来说,主要的函数是DriverEntry例程.卸载例程及各个IRP的派遣例程. 一.驱动加载过程与驱动入口函数(DriverEntry) 和编写普通应用程序一样,驱动程序有个入口函数,也 ...
- jquery.autocomplete 使用解析
页面引用 <script type="text/javascript" src="${base}/autocom/jquery-1.8.2.min.js" ...
- Redis.conf分析
Redis.conf 单位 配置文件对大小写不敏感 # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1m ...
- 论文笔记:(CVPR2019)PointWeb: Enhancing Local Neighborhood Features for Point Cloud Processing
目录 摘要 一.引言 二.相关工作 3D数据表示 点云深度学习 三.我们的方法 3.1 自适应特征调整(AFA)模块 3.1.1 影响函数fimp 3.1.2 关系函数frel 3.1.3 逐元素影响 ...