国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java線程那點事兒

silvertheo / 564人閱讀

摘要:然而對于一個和關(guān)聯(lián)的線程來說在線程被創(chuàng)建和更新他的之前會有一個小窗口,因此必須檢查這種情況為線程結(jié)構(gòu)體分配內(nèi)存并創(chuàng)建線程??赡苁?,因此我們耗盡了內(nèi)存太多的活躍線程。代碼執(zhí)行到這,線程還是狀態(tài),因為線程必須被創(chuàng)建者直接啟動。

引言

說到Thread大家都很熟悉,我們平常寫并發(fā)代碼的時候都會接觸到,那么我們來看看下面這段代碼是如何初始化以及執(zhí)行的呢?

public class ThreadDemo {

    public static void main(String[] args) {
        new Thread().start();
    }
}
初始化流程

代碼就一行很簡單,那么這行簡單的代碼背后做了那些事情呢?

初始化Thread這個類

首先JVM會去加載Thread的字節(jié)碼,初始化這個類,這里即調(diào)用下面這段代碼:

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing  does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
}

是個native方法,那么我們?nèi)タ纯磧?nèi)部實現(xiàn)是什么,具體的目錄是openjdk/jdk/src/share/native/java/lang/Thread.c, 下載地址

#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))

//JVM前綴開頭的方法,具體實現(xiàn)在JVM中
static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

#undef THD
#undef OBJ
#undef STE
#undef STR

//jclass cls即為java.lang.Thread
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

可以發(fā)現(xiàn)具體的實現(xiàn)都是由這些JVM開頭的方法決定的,而這幾個方法的具體實現(xiàn)都在hotspotsrcsharevmprimsjvm.cpp文件中,而RegisterNatives我目前的理解其實類似一個方法表,從Java方法到native方法的一個映射,具體的原理后面再研究。

初始化Thread對象

其實就是一些賦值,名字、線程ID這些,這兩個變量都是static,用synchronized修飾,保證線程安全性。

    public Thread() {
        //nextThreadNum就是變量的自增,用synchronized修飾保證可見性
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
        private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }
    
        private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        // 安全相關(guān)的一坨東西....

        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
    
    
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
創(chuàng)建并啟動線程
    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

這里start0()是個native方法,對應(yīng)jvm.cpp中的JVM_StartThread,我們看到很多方法都是JVM_ENTRY開頭,JVM_END結(jié)尾,類似于{}的作用,這里是將很多公共的操作封裝到了JVM_ENTRY里面.

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;

  // We cannot hold the Threads_lock when we throw an exception,
  // due to rank ordering issues. Example:  we might need to grab the
  // Heap_lock while we construct the exception.
  bool throw_illegal_thread_state = false;

  // We must release the Threads_lock before we can post a jvmti event
  // in Thread::start.
  {
    // 加鎖
    MutexLocker mu(Threads_lock);
    
    // 自從JDK 5之后 java.lang.Thread#threadStatus可以用來阻止重啟一個已經(jīng)啟動
    // 的線程,所以這里的JavaThread通常為空。然而對于一個和JNI關(guān)聯(lián)的線程來說,在線程
    // 被創(chuàng)建和更新他的threadStatus之前會有一個小窗口,因此必須檢查這種情況
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      // We could also check the stillborn flag to see if this thread was already stopped, but
      // for historical reasons we let the thread detect that itself when it starts running

      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      // 為C++線程結(jié)構(gòu)體分配內(nèi)存并創(chuàng)建native線程。從java取出的stack size是有符號的,因此這里
      // 需要進行一次轉(zhuǎn)換,避免傳入負數(shù)導(dǎo)致創(chuàng)建一個非常大的棧。
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz);

      // At this point it may be possible that no osthread was created for the
      // JavaThread due to lack of memory. Check for this situation and throw
      // an exception if necessary. Eventually we may want to change this so
      // that we only grab the lock if the thread was created successfully -
      // then we can also do this check and throw the exception in the
      // JavaThread constructor.
      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "prepare".
        native_thread->prepare(jthread);
      }
    }
  }

  if (throw_illegal_thread_state) {
    THROW(vmSymbols::java_lang_IllegalThreadStateException());
  }

  assert(native_thread != NULL, "Starting null thread?");

  if (native_thread->osthread() == NULL) {
    // No one should hold a reference to the "native_thread".
    delete native_thread;
    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
        "unable to create new native thread");
    }
    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
              "unable to create new native thread");
  }

  Thread::start(native_thread);

JVM_END

基本上這里就是先加鎖,做些檢查,然后創(chuàng)建JavaThread,如果創(chuàng)建成功的話會調(diào)用prepare(),然后是一些異常處理,沒有異常的話最后會啟動線程,那么下面我們先來看看JavaThread是如何被創(chuàng)建的。

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()
#if INCLUDE_ALL_GCS
  , _satb_mark_queue(&_satb_mark_queue_set),
  _dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
  if (TraceThreadEvents) {
    tty->print_cr("creating thread %p", this);
  }
  initialize();//這個方法其實就是一堆變量的初始化,不是Null就是0.
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  // Create the native thread itself.
  // %note runtime_23
  os::ThreadType thr_type = os::java_thread;
  // 根據(jù)傳進來的entry_point判斷要創(chuàng)建的線程的類型。
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread;
  os::create_thread(this, thr_type, stack_sz);
  // _osthread可能是Null,因此我們耗盡了內(nèi)存(太多的活躍線程)。我們需要拋出OOM,然而不能在這做,因為調(diào)用者可能
  // 還持有鎖,而所有的鎖都必須在拋出異常之前被釋放。
  // 代碼執(zhí)行到這,線程還是suspended狀態(tài),因為線程必須被創(chuàng)建者直接啟動。
}

void JavaThread::initialize() {
  // Initialize fields
  // ...
  set_thread_state(_thread_new); // 線程的初始狀態(tài)
 // ...

}

JavaThreadState記錄了線程記錄了線程正在執(zhí)行的代碼在哪一部分,這個信息可能會被安全點使用到(GC),最核心的有四種:

_thread_new 剛開始啟動,但還沒執(zhí)行初始化代碼,更可能還在OS初始化的層面

_thread_in_native 在native代碼中

_thread_in_vm 在vm中執(zhí)行

_thread_in_Java 執(zhí)行在解釋或者編譯后的Java代碼中

每個狀態(tài)都會對應(yīng)一個中間的轉(zhuǎn)換狀態(tài),這些額外的中間狀態(tài)使得安全點的代碼能夠更快的處理某一線程狀態(tài)而不用掛起線程。

enum JavaThreadState {
  _thread_uninitialized     =  0, // should never happen (missing initialization)
  _thread_new               =  2, // just starting up, i.e., in process of being initialized
  _thread_new_trans         =  3, // corresponding transition state (not used, included for completness)
  _thread_in_native         =  4, // running in native code
  _thread_in_native_trans   =  5, // corresponding transition state
  _thread_in_vm             =  6, // running in VM
  _thread_in_vm_trans       =  7, // corresponding transition state
  _thread_in_Java           =  8, // running in Java or in stub code
  _thread_in_Java_trans     =  9, // corresponding transition state (not used, included for completness)
  _thread_blocked           = 10, // blocked in vm
  _thread_blocked_trans     = 11, // corresponding transition state
  _thread_max_state         = 12  // maximum thread state+1 - used for statistics allocation
};

我們看到 os::create_thread(this, thr_type, stack_sz);這行代碼會去實際的創(chuàng)建線程,首先我們知道Java宣傳的是一次編譯,到處運行,那么究竟是怎么做到在不同的CPU、操作系統(tǒng)上還能夠保持良好的可移植性呢?

// 平臺相關(guān)的東東
#ifdef TARGET_OS_FAMILY_linux
# include "os_linux.hpp"
# include "os_posix.hpp"
#endif
#ifdef TARGET_OS_FAMILY_solaris
# include "os_solaris.hpp"
# include "os_posix.hpp"
#endif
#ifdef TARGET_OS_FAMILY_windows
# include "os_windows.hpp"
#endif
#ifdef TARGET_OS_FAMILY_aix
# include "os_aix.hpp"
# include "os_posix.hpp"
#endif
#ifdef TARGET_OS_FAMILY_bsd
# include "os_posix.hpp"
# include "os_bsd.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_x86
# include "os_linux_x86.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_sparc
# include "os_linux_sparc.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_zero
# include "os_linux_zero.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_x86
# include "os_solaris_x86.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_sparc
# include "os_solaris_sparc.hpp"
#endif
#ifdef TARGET_OS_ARCH_windows_x86
# include "os_windows_x86.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_arm
# include "os_linux_arm.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_ppc
# include "os_linux_ppc.hpp"
#endif
#ifdef TARGET_OS_ARCH_aix_ppc
# include "os_aix_ppc.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_x86
# include "os_bsd_x86.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_zero
# include "os_bsd_zero.hpp"
#endif

我們看到os.hpp中有這樣一段代碼,能夠根據(jù)不同的操作系統(tǒng)選擇include不同的頭文件,從而將平臺相關(guān)的邏輯封裝到對應(yīng)的庫文件中,我們這里以linux為例,create_thread最終會調(diào)用os_linux.cpp中的create_thread方法。

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
  assert(thread->osthread() == NULL, "caller responsible");

  // Allocate the OSThread object
  OSThread* osthread = new OSThread(NULL, NULL);
  if (osthread == NULL) {
    return false;
  }

  // set the correct thread state
  osthread->set_thread_type(thr_type);

  // 初始狀態(tài)為ALLOCATED,而不是INITIALIZED
  osthread->set_state(ALLOCATED);

  thread->set_osthread(osthread);

  // init thread attributes
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  // stack size
  if (os::Linux::supports_variable_stack_size()) {
    // 如果上層未傳遞則計算stack_size
    if (stack_size == 0) {
      //如果為compiler_thread,則分配4M,否則默認會分配1M
      stack_size = os::Linux::default_stack_size(thr_type);

      switch (thr_type) {
      case os::java_thread:
        // Java線程用ThreadStackSize,這個值可以通過-Xss指定
        assert (JavaThread::stack_size_at_create() > 0, "this should be set");
        stack_size = JavaThread::stack_size_at_create();
        break;
      case os::compiler_thread:
        if (CompilerThreadStackSize > 0) {
          stack_size = (size_t)(CompilerThreadStackSize * K);
          break;
        } // else fall through:
          // use VMThreadStackSize if CompilerThreadStackSize is not defined
      case os::vm_thread:
      case os::pgc_thread:
      case os::cgc_thread:
      case os::watcher_thread:
        if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
        break;
      }
    }
        
    // 用兩者較大的那個,min_stack_allowed默認為128K
    stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
    pthread_attr_setstacksize(&attr, stack_size);
  } else {
    // let pthread_create() pick the default value.
  }

  // glibc guard page
  pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));

  ThreadState state;

  {
    // 檢查是否需要加鎖
    bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
    if (lock) {
      os::Linux::createThread_lock()->lock_without_safepoint_check();
    }

    pthread_t tid;
    // Linux用于創(chuàng)建線程的函數(shù),這個線程通過執(zhí)行java_start來啟動,其中thread是作為java_start的參數(shù)傳遞進來的
    // 具體可見手冊:http://man7.org/linux/man-pages/man3/pthread_create.3.html
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

    pthread_attr_destroy(&attr);

    if (ret != 0) {
      // 創(chuàng)建失敗,將_osthread置為空,還記得在jvm.cpp的JVM_StartThread中會根據(jù)_osthread是否為空來判斷
      // 是否創(chuàng)建成功
      if (PrintMiscellaneous && (Verbose || WizardMode)) {
        perror("pthread_create()");
      }
      // 清理資源,并解鎖
      thread->set_osthread(NULL);
      delete osthread;
      if (lock) os::Linux::createThread_lock()->unlock();
      return false;
    }

    // 創(chuàng)建成功會將底層線程的ID保存在tid中
    osthread->set_pthread_id(tid);

    // 等待子線程創(chuàng)建完成或者終止
    {
      Monitor* sync_with_child = osthread->startThread_lock();
      MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
      while ((state = osthread->get_state()) == ALLOCATED) {
        sync_with_child->wait(Mutex::_no_safepoint_check_flag);
      }
    }

    if (lock) {
      os::Linux::createThread_lock()->unlock();
    }
  }

  // 線程的數(shù)目達到極限了
  if (state == ZOMBIE) {
      thread->set_osthread(NULL);
      delete osthread;
      return false;
  }

  // The thread is returned suspended (in state INITIALIZED),
  // and is started higher up in the call chain
  assert(state == INITIALIZED, "race condition");
  return true;
}

下面我們來看看pthread_create會執(zhí)行的回調(diào)函數(shù)java_start,這個方法是所有新創(chuàng)建的線程必走的流程。

static void *java_start(Thread *thread) {
  // 嘗試隨機化熱棧幀高速緩存行的索引,這有助于優(yōu)化擁有相同棧幀線程去互相驅(qū)逐彼此的緩存行時,線程
  // 可以是同一個JVM實例或者不同的JVM實例,這尤其有助于擁有超線程技術(shù)的處理器。
  static int counter = 0;
  int pid = os::current_process_id();
  alloca(((pid ^ counter++) & 7) * 128);

  ThreadLocalStorage::set_thread(thread);

  OSThread* osthread = thread->osthread();
  Monitor* sync = osthread->startThread_lock();

  // non floating stack LinuxThreads needs extra check, see above
  if (!_thread_safety_check(thread)) {
    // notify parent thread
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
    osthread->set_state(ZOMBIE);
    sync->notify_all();
    return NULL;
  }

  // thread_id is kernel thread id (similar to Solaris LWP id)
  osthread->set_thread_id(os::Linux::gettid());

  if (UseNUMA) {
    int lgrp_id = os::numa_get_group_id();
    if (lgrp_id != -1) {
      thread->set_lgrp_id(lgrp_id);
    }
  }
  // initialize signal mask for this thread
  os::Linux::hotspot_sigmask(thread);

  // initialize floating point control register
  os::Linux::init_thread_fpu_state();

  // handshaking with parent thread
  {
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);

    // 設(shè)置為已經(jīng)初始化完成,并notify父線程
    osthread->set_state(INITIALIZED);
    sync->notify_all();

    // wait until os::start_thread()
    while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }
  }
    
    
  //這里上層傳遞過來的是JavaThread,因此會調(diào)用JavaThread#run()方法  
  thread->run();

  return 0;
}


void JavaThread::run() {
  // 初始化本地線程分配緩存(TLAB)相關(guān)的屬性
  this->initialize_tlab();

  // used to test validitity of stack trace backs
  this->record_base_of_stack_pointer();

  // Record real stack base and size.
  this->record_stack_base_and_size();

  // Initialize thread local storage; set before calling MutexLocker
  this->initialize_thread_local_storage();

  this->create_stack_guard_pages();

  this->cache_global_variables();

  // 將線程的狀態(tài)更改為_thread_in_vm,線程已經(jīng)可以被VM中的安全點相關(guān)的代碼處理了,也就是說必須
  // JVM如果線程在執(zhí)行native里面的代碼,是搞不了安全點的,待確認
  ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);

  assert(JavaThread::current() == this, "sanity check");
  assert(!Thread::current()->owns_locks(), "sanity check");

  DTRACE_THREAD_PROBE(start, this);

  // This operation might block. We call that after all safepoint checks for a new thread has
  // been completed.
  this->set_active_handles(JNIHandleBlock::allocate_block());

  if (JvmtiExport::should_post_thread_life()) {
    JvmtiExport::post_thread_start(this);
  }

  EventThreadStart event;
  if (event.should_commit()) {
     event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
     event.commit();
  }

  // We call another function to do the rest so we are sure that the stack addresses used
  // from there will be lower than the stack base just computed
  thread_main_inner();

  // Note, thread is no longer valid at this point!
}

void JavaThread::thread_main_inner() {
  assert(JavaThread::current() == this, "sanity check");
  assert(this->threadObj() != NULL, "just checking");

  // Execute thread entry point unless this thread has a pending exception
  // or has been stopped before starting.
  // Note: Due to JVM_StopThread we can have pending exceptions already!
  if (!this->has_pending_exception() &&
      !java_lang_Thread::is_stillborn(this->threadObj())) {
    {
      ResourceMark rm(this);
      this->set_native_thread_name(this->get_thread_name());
    }
    HandleMark hm(this);
    // 這個entry_point就是JVM_StartThread中傳遞過來的那個,也就是thread_entry
    this->entry_point()(this, this);
  }

  DTRACE_THREAD_PROBE(stop, this);

  this->exit(false);
  delete this;
}

我們最后再看thread_entry的代碼

static void thread_entry(JavaThread* thread, TRAPS) {
  HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  JavaCalls::call_virtual(&result,
                          obj,
                          KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                          vmSymbols::run_method_name(),
                          vmSymbols::void_method_signature(),
                          THREAD);
}

vmSymbols,這個是JVM對于那些需要特殊處理的類、方法等的聲明,我的理解就是一個方法表,根據(jù)下面這行代碼可以看出來,其實調(diào)用的就是run()方法.

  /* common method and field names */                                                            
  template(run_method_name,                           "run")                                  

然后我們回到JVM_StartThread方法中,這里會接著調(diào)用prepare()方法,設(shè)置線程優(yōu)先級(將Java中的優(yōu)先級映射到os中),然后添加到線程隊列中去.最后會調(diào)用Thread::start(native_thread);
啟動線程。

void Thread::start(Thread* thread) {
  trace("start", thread);
  // start和resume不一樣,start被synchronized修飾
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
     // 在啟動線程之前初始化線程的狀態(tài)為RUNNABLE,為啥不能在之后設(shè)置呢?因為啟動之后可能是
     //  等待或者睡眠等其他狀態(tài),具體是什么我們不知道
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}
總結(jié)

一個Java線程對應(yīng)一個JavaThread->OSThread -> Native Thread

在調(diào)用java.lang.Thread#start()方法之前不會啟動線程,僅僅調(diào)用run()方法只是會在當前線程運行而已

//todo

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/70152.html

相關(guān)文章

  • 我終于搞清楚了和String有關(guān)的那點事兒

    摘要:為了減少在中創(chuàng)建的字符串的數(shù)量,字符串類維護了一個字符串常量池。但是當執(zhí)行了方法后,將指向字符串常量池中的那個字符串常量。由于和都是字符串常量池中的字面量的引用,所以。究其原因,是因為常量池要保存的是已確定的字面量值。 String,是Java中除了基本數(shù)據(jù)類型以外,最為重要的一個類型了。很多人會認為他比較簡單。但是和String有關(guān)的面試題有很多,下面我隨便找兩道面試題,看看你能不能...

    paulli3 評論0 收藏0
  • Javascripit類型轉(zhuǎn)換比較那點事兒,雙等號(==)

    摘要:同理,若為,返回的結(jié)果若為或者,且為,返回的結(jié)果。同理,若為或者,且為,返回的結(jié)果是對象轉(zhuǎn)換基本類型的方法??磦€例子根據(jù)上述規(guī)則來解析為,上式為第條上式為第條上式為,上式為第條上式為 前不久因為一個項目設(shè)計的問題,煩心了好幾天,為了不留坑擁抱強類型語言特點,還是選擇了===作為數(shù)據(jù)判斷是否相等,對于==今天來探究一下貓膩(弱類型的JavaScript的坑真的太多了,typescript...

    Steve_Wang_ 評論0 收藏0
  • form表單那點事兒(下) 進階篇

    摘要:在表單提交時,瀏覽器會自動進行一系列的校驗工作,沒有通過校驗的表單是無法提交到服務(wù)器的。而方法提交表單,會在請求中發(fā)送表單字段鍵值對。表單提交事件表單提交到服務(wù)器時,會觸發(fā)事件。 上一篇主要溫習(xí)了一下form表單的屬性和表單元素,這一片主要講解用JavaScript如何操作form。 表單操作 取值 用JavaScript操作表單,免不了會有取值賦值操作,比如有以下表單: ...

    jerryloveemily 評論0 收藏0
  • form表單那點事兒(上) 基礎(chǔ)篇

    摘要:用于或元素時,將提交指定的表單示例代碼只能上傳圖片只能上傳視頻使用的屬性,是一個類型的值,或文件后綴名。在以前,要想改變表單元素外觀,需要通過其他標簽來模擬。以下點到名的表單元素,還是可以照常使用的。 做為html中最為常見,應(yīng)用最廣泛的標簽之一,form常伴隨前端左右。了解更深,用的更順。 表單屬性 這個表單展示了form表單常用的屬性 屬性名 屬性值 ...

    Forest10 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<